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:
internal class Person
{
public Person() { }
public Person(string name, int age, string address)
this.Name = name;
this.Age = age;
this.Address = address;
}
public string Name {get;set;}
public int Age {get;set;}
public string Address {get;set;}
……
If we want to save a person to a stream, we could add a Serialize and a Deserialize method:
public void Serialize(Stream stream){
using (BinaryWriter writer = new BinaryWriter(stream)){
writer.Write(Name);
writer.Write(Age);
writer.Write(Address);
// closing the writer will also close the underlying stream
internal static Person Deserialize(Stream stream) {
Person p = new Person();
using (BinaryReader reader = new BinaryReader(stream)) {
p.Name = reader.ReadString();
p.Age = reader.ReadInt32();
p.Address = reader.ReadString();
return p;
Similar, we could serialize and deserialize from byte[] (new code in bold):
public void Serialize(byte[] buf, ref int bufLength){
using (MemoryStream s = new MemoryStream()){
using (BinaryWriter writer = new BinaryWriter(s)){
buf = s.GetBuffer();
bufLength = (int)s.Length;
// closing the writer will close the underlying stream
internal static Person Deserialize(byte[] buf) {
using (MemoryStream stream = new MemoryStream(buf)) {
using (BinaryReader reader = new BinaryReader(stream)){
And here comes the beautiful part. If we want to add compression / decompression on top of this, it’s this simple:
public void SerializeAndCompress(ref byte[] buf, ref int bufLength)
using (MemoryStream s = new MemoryStream()) {
using (BinaryWriter writer = new BinaryWriter(s)) {
byte[] uncompressedBuf = s.GetBuffer();
int uncompressedLength = (int)s.Length;
// truncate the stream to write the new compressed data
s.SetLength(0);
using (DeflateStream zipStream = new DeflateStream(s, CompressionMode.Compress, true)) {
// compress the serialized bytes
zipStream.Write(uncompressedBuf, 0, uncompressedLength);
internal static Person DecompressAndDeserialize(byte[] buf) {
// first decompress, then deserialize the bytes
using (DeflateStream zipStream = new DeflateStream(stream, CompressionMode.Decompress, true)) {
return Person.Deserialize(zipStream);
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.