0

I want to compress a ProtoBuffer object on serialisation and decompress on deserialisation. Unfortunatly, C# stdlib offers only compression routines that work on streams rather than on byte[], that makes it a bit unesseray more verbose than a function call. My Code so far:

class MyObject{
 public string P1 {get; set;}
 public string P2 {get; set;}
 // ...

 public byte[] Serialize(){
   var builder = new BinaryFormat.MyObject.Builder();
   builder.SetP1(P1);
   builder.SetP2(P2);
   // ...

   // object is now build, let's compress it.
   var ms = new MemoryStream();
   // Without this using, the serialisatoin/deserialisation Tests fail
   using (var gz = new GZipStream(ms, CompressionMode.Compress))
   {
     builder.Build().WriteTo(gz);
   }
   return ms.ToArray();
 }

 public void Deserialize(byte[] data)
 {
   var ms = new MemoryStream();
   // Here, Tests work, even when the "using" is left out, like this:
   (new GZipStream(new MemoryStream(data), CompressionMode.Decompress)).CopyTo(ms);
   var msg = BinaryFormat.MachineInfo.ParseFrom(ms.ToArray());

   P1 = msg.P1;
   P2 = msg.P2;
   // ...
 }
}

When dealing with streams, it seems one has to manually take care of the disposal of the objects. I wonder why that is, I'd expect GZipStream to be fully managed Code. And I wonder If Deserialize works only by accident and if I should dispose the MemoryStreams aswell.

I know I could probably solve this problem by simply using a thrid party compression library, but that's somewhat besides the point of this question.

Michael K.
  • 423
  • 1
  • 4
  • 13
  • If I remember rightly a `MemoryStream` is disposed of when it's owning object, in this case the `GZipStream` is disposed. Or that might be just for `Image`s... – TheLethalCoder Jan 18 '17 at 16:32
  • Well take it as a learning curve, If something implements `IDisposable` it should be disposed of, ideally using `using`. Write code to properly use, and dispose of, objects and you wouldn't have seen the issue in the first place. – TheLethalCoder Jan 18 '17 at 16:38
  • @TheLethalCoder [not always](https://blogs.msdn.microsoft.com/pfxteam/2012/03/25/do-i-need-to-dispose-of-tasks/), take `Task` for example, it is IDisposeable but it's page for the function says [you don't need to do it](https://msdn.microsoft.com/en-us/library/dd270681(v=vs.110).aspx#Anchor_2) when targeting .NET 4.5 or later. – Scott Chamberlain Jan 18 '17 at 16:39
  • @ScottChamberlain It was more of a generic comment, as always there are edge cases but they are out of scope of the question – TheLethalCoder Jan 18 '17 at 16:41
  • Shouldn't the compiler warn me about this pitfall? For me it was not obvious that GZipStream even implements IDisposable. This BTW feels very strange in a GC language. – Michael K. Jan 18 '17 at 16:49

1 Answers1

2

GZipStream needs to be disposed so it flushes it's final blocks of compression out of its buffer to its underlying stream, it also calls dispose on the stream you passed in unless you use the overload that takes in a bool and you pass in false.

If you where using the overload that did not dispose of the MemoryStream it is not as critical to have the MemoryStream be disposed because it is not writing its internall buffer anywhere. The only thing it does is set some flags and set a Task object null so it can be GCed sooner if the stream lifetime is longer than the dispose point.

    protected override void Dispose(bool disposing)
    {
        try {
            if (disposing) {
                _isOpen = false;
                _writable = false;
                _expandable = false;
                // Don't set buffer to null - allow TryGetBuffer, GetBuffer & ToArray to work.
#if FEATURE_ASYNC_IO
                _lastReadTask = null;
#endif
            }
        }
        finally {
            // Call base.Close() to cleanup async IO resources
            base.Dispose(disposing);
        }
    }

Also, although the comment says "Call base.Close() to cleanup async IO resources" the base dispose function from the Stream class does nothing at all.

    protected virtual void Dispose(bool disposing)
    {
        // Note: Never change this to call other virtual methods on Stream
        // like Write, since the state on subclasses has already been 
        // torn down.  This is the last code to run on cleanup for a stream.
    }

All that being said, when decompressing a GZipStream you can likely get away with not disposing it for the same reason as not disposing the MemoryStream, when decompressing it does not buffer bytes anywhere so there is no need to flush any buffers.

Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431