Offenen Ströme

… und andere tiefe C#-Gewässer

esskar über Programmierung

Streams sind seit Version 1.0 fester Bestandteil des .NET Frameworks. Sie erlauben einen unkomplizierten Zugriff auf verschiedene I/O Layer wie Dateien, Sockets oder sogar PrintQueues. Dabei ist man jedoch ersteinmal auf das Schreiben und Lesen von Bytes beschränkt.

Um Strings, Bools oder Zahlen auf den Stream zu schreiben benutzt man entweder System.BitConverter.GetBytes und System.Text.Encoding (was mühseelig sein kann) oder man bedient sich dem BinaryWriter aus dem System.IO Namensraum.
Leider hat der BinaryWriter (als auch sein Gegenstück der BinaryReader) ein nicht unbedenkliches Problem: der übergebene Stream wird beim Dispose ebenfalls geschlossen. In einigen Fällen ist dies oft erwünscht:


using(BinaryWriter bw = new BinaryWriter(File.Open(fileName, FileMode.Create)))
{
   // schreibe etwas mit Hilfe von bw.Write
}

Aber hat man z.B. einen Socket einer ständig offenen Verbinung zu einem Client will man ggf. je nach Protokol anderes auf den Socket schreiben als es der BinaryWriter vorgibt.


// ns ist ein NetworkStream
using(BinaryWriter bw = new BinaryWriter(ns))
{
   // schreibe etwas mit Hilfe von bw.Write
}
using(FileStream fs = new FileStream.Open(fileName))
{
   // lese Datei und 
   // schreibe sie auf den NetworkStream (ab .NET 4)
   fs.CopyTo(ns); // << Exception, da BinaryWriter den 
                  // Stream schon geschlossen hat
}

Um dieses Problem zu umgehen, benutze ich NonClosingStreamProxy (s.u.):


// ns ist ein NetworkStream
using(BinaryWriter bw = new BinaryWriter(new NonClosingStreamProxy(ns)))
{
   // schreibe etwas mit Hilfe von bw.Write
}
using(FileStream fs = new FileStream.Open(fileName))
{
   // lese Datei und schreibe sie auf den NetworkStream 
   // (CopyTo ab .NET 4)
   fs.CopyTo(ns);
}

Dem NonClosingStreamProxy übergibt man im Konstruktur den eigentlichen Stream. Nun leitet er alle Stream-Operationen (Lesen, Schreiben, Suchen, etcpp.) an diesen Stream weiter. Nur Close/Dispose wird nicht weiter gegeben, so dass der Original-Stream weiterhin benutzt werden kann.


namespace System.IO
{
    public sealed class NonClosingStreamProxy : Stream
    {
        private bool m_closed = false;

        public NonClosingStreamProxy(Stream stream)
        {
            if (stream == null)
                throw new ArgumentNullException("stream");
            this.BaseStream = stream;
        }        

        public Stream BaseStream
        {
            get; private set;
        }

        private void Invalidate()
        {
            if (m_closed)
                throw new InvalidOperationException("Already closed or disposed.");
        }        

        public override object InitializeLifetimeService()
        {
            throw new NotSupportedException();
        }

        public override bool CanRead
        {
            get { return m_closed ? false : this.BaseStream.CanRead; }
        }

        public override bool CanSeek
        {
            get { return m_closed ? false : this.BaseStream.CanSeek; }
        }

        public override bool CanTimeout
        {
            get { return m_closed ? false : this.BaseStream.CanTimeout; }
        }

        public override bool CanWrite
        {
            get { return m_closed ? false : this.BaseStream.CanWrite; }
        }

        public override void Flush()
        {
            this.Invalidate();
            this.BaseStream.Flush();
        }

        public override long Length
        {
            get 
            {
                this.Invalidate();
                return this.BaseStream.Length;
            }
        }

        public override long Position
        {
            get
            {
                this.Invalidate();
                return this.BaseStream.Position;
            }
            set
            {
                this.Invalidate();
                this.BaseStream.Position = value;
            }
        }

        public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
        {
            this.Invalidate();
            return this.BaseStream.BeginRead(buffer, offset, count, callback, state);
        }

        public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
        {
            this.Invalidate();
            return this.BaseStream.BeginWrite(buffer, offset, count, callback, state);
        }

        public override void Close()
        {
            if (!m_closed)
            {
                this.Flush();
                m_closed = true;
            }
        }

        public override System.Runtime.Remoting.ObjRef CreateObjRef(Type requestedType)
        {
            throw new NotSupportedException();
        }        

        public override int EndRead(IAsyncResult asyncResult)
        {
            this.Invalidate();
            return this.BaseStream.EndRead(asyncResult);
        }

        public override void EndWrite(IAsyncResult asyncResult)
        {
            this.Invalidate();
            this.BaseStream.EndWrite(asyncResult);
        }        

        public override int Read(byte[] buffer, int offset, int count)
        {
            this.Invalidate();
            return this.BaseStream.Read(buffer, offset, count);
        }

        public override int ReadByte()
        {
            this.Invalidate();
            return this.BaseStream.ReadByte();
        }

        public override int ReadTimeout
        {
            get
            {
                this.Invalidate();
                return this.BaseStream.ReadTimeout;
            }
            set
            {
                this.Invalidate();
                this.BaseStream.ReadTimeout = value;
            }
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            this.Invalidate();
            return this.BaseStream.Seek(offset, origin);
        }

        public override void SetLength(long value)
        {
            this.Invalidate();
            this.BaseStream.SetLength(value);
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            this.Invalidate();
            this.BaseStream.Write(buffer, offset, count);
        }

        public override void WriteByte(byte value)
        {
            this.Invalidate();
            this.BaseStream.WriteByte(value);
        }

        public override int WriteTimeout
        {
            get
            {
                this.Invalidate();
                return this.BaseStream.WriteTimeout;
            }
            set
            {
                this.Invalidate();
                this.BaseStream.WriteTimeout = value;
            }
        }        
    }
}

Informationen über den Autor

esskar

ist das, was man einen Fullstack Developer bezeichnet. Vom Design bis in die Tiefen der C-Programmierung verfügt er nationale und internationale Projekterfahrung, und alles unter dem Motto — Code ist Poesie. Follow @esskar