P3.NET

Reading and Writing to Streams

Streams in .NET are prevalent. Most everything that requires input or output accepts a stream. The issue with streams is that they are too generic. They only support reading and writing bytes (or byte arrays). Since streams can be read only or write only this makes some sense. The reality though is that most times you know whether a stream is readable or writable. If worse comes to worse you can query the stream for read and write access. But you’re still stuck with reading and writing bytes. To make working with streams easier Microsoft introduced the BinaryReader/BinaryWriter types. These stream read/writers allow you to read and write CLR types to an underlying stream. The theory being that the code is more readable if you explicitly create a reader or writer. Here is an example of writing some data to a stream.

static void Main ( string[] args )
{
   using (var stream = new MemoryStream())
   {
      WriteNumbers(stream);
      WriteText(stream);
   };
}

static void WriteNumbers ( Stream stream )
{
   using (var writer = new BinaryWriter(stream)) 
   {
      writer.Write(10);
   };
}

static void WriteText ( Stream stream )
{
   using (var writer = new BinaryWriter(stream)) 
   {
      writer.Write("Hello");
   };
}

What are the problems with this code? Well first of all it is not very clean. Using statements are important for cleaning up resources but I believe that they are infrastructure that should be hidden whenever possible. Secondly when the writer is closed so is the stream. That’s right, once you have finished reading or writing your stream will be cleaned up automatically. For the original purpose of readers/writers this might have made sense but now it just complicates things. In most of the code I write a higher level method opens the stream and then passes the stream to lower level methods to do the read/write functionality. In order to do that with the above code I would need to pass a BinaryReader/BinaryWriter to the methods. But these types are implementation details to me so passing them around seems wrong.

static void Main ( string[] args )
{
   using (var stream = new MemoryStream())
   {
      using (var writer = new BinaryWriter(stream)) 
      {
         WriteNumbers(writer);
         WriteText(writer);
      };
   };
}

static void WriteNumbers ( BinaryWriter writer )
{
   writer.Write(10);
}

static void WriteText ( BinaryWriter writer )
{
   writer.Write("Hello");
}

There are a couple of ways to clean up this code to make it better. The first approach is to stick with the explicit reader/writer code but ensure that the stream doesn’t get closed. Then we can pass the stream to other methods and if they need to read or write they can create the appropriate instances. But to do that we either have to ensure the methods don’t close their reader/writer or we need to prevent the stream from closing. The latter approach can be easily accomplished by creating a simple aggregate stream type that wraps an existing stream.  The aggregate stream, called NonclosingStream in my case, passes all requests to the wrapped stream except for closing. The wrapper stream ignores requests to close. The wrapper stream is what is passed to lower level methods. If the lower level methods use a reader/writer and it closes the stream nothing will happen. Here’s how the updated code would look.

static void Main ( string[] args )
{
   using (var stream = new MemoryStream())
   {
      var wrapper = new NonclosingStream(stream);

      WriteNumbers(wrapper);
      WriteText(wrapper);
   };
}

static void WriteNumbers ( Stream stream )
{
   using (var writer = new BinaryWriter(stream)) 
   {
      writer.Write(10);
   };
}

static void WriteText ( Stream stream )
{
   using (var writer = new BinaryWriter(stream)) 
   {
      writer.Write("Hello");
   };
}

The code hasn’t changed much from the first sample but it now works and lower level methods can use a reader/writer without causing problems. But this is still too much code. With the advent of extension methods we can clean this code up by adding extension methods to the Stream class directly. One could argue that we are now cluttering up the class namespace with potentially unusable methods but since streams already have read/write capabilities I think the argument is mute. Here’s the cleaned up version.

static void Main ( string[] args )
{
   using (var stream = new MemoryStream())
   {
      WriteNumbers(stream);
      WriteText(stream);
   };
}

static void WriteNumbers ( Stream stream )
{
   stream.Write(10);
}

static void WriteText ( Stream stream )
{
   stream.WriteCompressedStream("Hello");
}

Now the code is cleaned up, the higher level method does not have to do anything different and lower level methods can read and write as needed. The StreamExtensions class provides the same functionality as BinaryReader/BinaryWriter without the need for a separate object. For string types there are extensions to support a variety of string formats including null terminated strings, fixed length strings, length prefix strings and compressed strings.

The download includes the source code for NonclosingStream, StreamExtensions and their unit tests. Feel free to use them in your code.

Download the Library Code