Saturday, March 06, 2010

System.IO.Compression and LeaveOpen

I got into the habit of stacking up blocks of using statements to avoid lots of nesting and indentation in my code, ultimately making it more readable to humans!

using (MemoryStream stream = new MemoryStream())
using (DeflateStream deflater = new DeflateStream(stream, CompressionMode.Compress))
using (StreamWriter writer = new StreamWriter(deflater))
{
// write strings into a compressed memory stream
}


The above example shows where this doesn't work though. The intention was to compress string data into an in-memory buffer, but it wasn't working as expected. There was a bug in my code!

When you're using a DeflateStream or a GZipStream, you're (hopefully) writing more bytes into it than it's writing to its underlying stream. You may choose to Flush() it but both streams are still open and you can continue to write data into the compression stream. Until the compression stream is closed, however, the underlying stream will not contain completely valid and readable data. When you Close() it, it writes out the final bytes that make the stream valid. By default, the behaviour of the compression stream is to Close() the underlying stream when it's closed, but when that underlying stream is a MemoryStream this leaves you with a valid compressed byte stream somewhere in memory that's inaccessible!

What you need to do instead is leave the underlying stream open, using the extra constructor argument on the compression stream, like this:

using (MemoryStream stream = new MemoryStream())
{
using (DeflateStream deflater = new DeflateStream(stream, CompressionMode.Compress, true))
using (StreamWriter writer = new StreamWriter(deflater))
{
// write strings into a compressed memory stream
}
// access the still-open memory stream's buffer
}

No comments: