The System.IO namespace contains types that allow reading and writing to files and data streams, and types that provide basic file and directory support.
Looking at all the streams classes, I sometimes get this overwhelmed feeling: which one do I use? When writing/reading text or binary files it’s pretty easy, but what if I need to apply compression and encryption and other operations?
The C# 3.0 in a Nutshell book (written by Joseph Albahari; Ben Albahari) has a great chapter describing streams. The System.IO.Stream class provides a generic view of a sequence of bytes – and usually we don’t use it directly. The important thing to remember is that there are 3 layers:
1. Backing store stream – the classes that talk directly with the physical stores. These classes work with bytes (raw data). Some classes:
· FileStream Exposes a Stream around a file, supporting both synchronous and asynchronous read and write operations.
· MemoryStream Creates a stream whose backing store is memory.
· UnmanagedMemoryStream Provides access to unmanaged blocks of memory from managed code.
2. Stream decorators – they can be applied on top of backing store streams to transform the data in some way (like adding compression and encryption). Again they work with bytes. Some classes are:
· DeflateStream Provides methods and properties for compressing and decompressing streams using the Deflate algorithm (System.IO.Compression namespace).
· GZipStream Provides methods and properties used to compress and decompress streams (System.IO.Compression namespace).
· CryptoStream Defines a stream that links data streams to cryptographic transformations (System.Security.Cryptography namespace)
· BufferedStream Adds a buffering layer to read and write operations on another stream. This class cannot be inherited.
3. Stream adapters – the high level classes that provide abstractions on top of the backing store streams and decorators. Some classes:
· BinaryReader Reads primitive data types as binary values in a specific encoding.
· BinaryWriter Writes primitive types in binary to a stream and supports writing strings in a specific encoding.
· StreamReader Implements a TextReader that reads characters from a byte stream in a particular encoding.
· StreamWriter Implements a TextWriter for writing characters to a stream in a particular encoding.
We can chain streams – by passing a stream in the constructor of another stream. This way, we can accomplish multiple operations easily.
Let’s say we have a simple class called Person:
If we want to save a person to a stream, we could add a Serialize and a Deserialize method:
Similar, we could serialize and deserialize from byte (new code in bold):
And here comes the beautiful part. If we want to add compression / decompression on top of this, it’s this simple:
So, the important thing to remember: apply stream adapters on top of stream decorators and backing store streams. Chaining the streams can accomplish all stream operations your heart desires.