6

I have found the following construct in some open-source code:

var mstream = new MemoryStream();
// ... write some data to mstream
mstream.Close();
byte[] b = mstream.GetBuffer();

I thought this code would have "unexpected" behavior and maybe throw an exception, since the call to Close should effectively be a call to Dispose according to the MSDN documentation.

However, as far as I have been able to tell from experimenting, the call to GetBuffer() always succeeds and returns a valid result, even if I Thread.Sleep for 20 seconds or enforce garbage collection via GC.Collect().

Should the call to GetBuffer() succeed even after Close/Dispose? In that case, why is not the underlying buffer released in the MemoryStream disposal?

Anders Gustafsson
  • 15,837
  • 8
  • 56
  • 114
  • 2
    Just a side-note - `GetBuffer` will get you the whole buffer, including any padding that's there for growth. If the stream wasn't initialized with fixed capacity, this probably means you've got zeroes at the end you don't want. That said, `GetBuffer` only works when the buffer size is fixed, if I'm reading the code correctly. – Luaan May 26 '14 at 08:11

4 Answers4

5

Technically there's nothing to dispose in MemoryStream. Literally nothing, it doesn't have operating system handles, unmanaged resources, nothing. it is just a wrapper around byte[]. All what you can do is set the buffer(internal array) to null, that BCL team hasn't done for some reason.

As @mike noted in comments BCL team wanted GetBuffer and ToArray to work, even after disposed, though we're not sure why?. Reference source.

Here's how Dispose implemented.

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

and GetBuffer is below

public virtual byte[] GetBuffer()
{
    if (!this._exposable)
    {
        throw new UnauthorizedAccessException(Environment.GetResourceString("UnauthorizedAccess_MemStreamBuffer"));
    }
    return this._buffer;
}

As you can see in Dispose _buffer is untouched, and in GetBuffer no disposed checks.

Sriram Sakthivel
  • 72,067
  • 7
  • 111
  • 189
  • 4
    In fact, the [reference source](http://referencesource.microsoft.com/#mscorlib/system/io/memorystream.cs#0b83d17ca69bf8ea#references) has comment about this: "Don't set buffer to null - allow GetBuffer & ToArray to work." – Mike Zboray May 26 '14 at 08:07
  • The reason the `buffer` isn't set to null is probably that it doesn't have any effect on garbage collection in normal operation (you'd use the memory stream in a using, so the `buffer` would be out of scope as soon as the stream itself). Also, when you use `MemoryStream` in a stream chain, you want to be able to get to the data after you close everything. – Luaan May 26 '14 at 08:07
  • @mikez Thanks, I was looking into reflector. Updated my answer with reference source. – Sriram Sakthivel May 26 '14 at 08:12
5
  1. It doesn't need to. The buffer is managed memory, so normal garbage collection will deal with it, without needing to be included in the disposal.
  2. It's useful to be able to get the bytes of the memory stream, even after the stream has been closed (which may have happened automatically after the steam was passed to a method that writes something to a stream and then closes said stream). And for that to work, the object needs to hold onto the buffer along with a record of how much had been written to it.

In considering the second point, it actually makes more sense in a lot of cases to call ToArray() (which as said, requires the in-memory store that GetBuffer() returns to still be alive) after you've closed the stream, because having closed the stream guarantees that any further attempt to write to the stream will fail. Hence if you have a bug where you obtain the array too early, it will throw an exception rather than just give you incorrect data. (Obviously if you explicitly want to get the current array part-way through the stream operations, that's another matter). It also guarantees that all streams are fully flushed rather than having part of their data in a temporary buffer (MemoryStream isn't buffered because MemoryStream essentially is a buffer, but you may have been using it with chained streams or writers that had their own separate buffer).

Jon Hanna
  • 110,372
  • 10
  • 146
  • 251
1

Since the GC is non-deterministic you cannot force it to immediately dispose the MemoryStream, thus the instance will not be marked as disposed immediately, instead it will just be marked for disposal. This means that for some time, until it is really disposed you can use some of its functions. Since it keeps a strong reference to its buffer you can get it, here is what the GetBuffer method looks like:

public virtual byte[] GetBuffer()
{
    if (!this._exposable)
    {
        throw new UnauthorizedAccessException(Environment.GetResourceString("UnauthorizedAccess_MemStreamBuffer"));
    }
    return this._buffer;
}
Georgi-it
  • 3,676
  • 1
  • 20
  • 23
1

Unlike most interface methods, the IDisposable.Dispose does not promise to do anything. Instead, it provides a standard means by which the owner of an object can let that object know that its services are no longer required, in case the object might need to make use of that information. If an object has asked outside entities to do something on its behalf, and has promised those outside entities that it will let them know when their services are no longer required, its Dispose method can relay the notification to those entities.

If an object has a method which can only be performed while the object has outside entities acting on its behalf, an attempt to call that method after those entities have been dismissed should throw an ObjectDisposedException rather than failing in some other way. Further, if there is a method which cannot possibly be useful after the entity is dismissed, it should often throw ObjectDisposedException even if a particular didn't actually need to use the entity. On the other hand, if a particular call would have a sensible meaning after an object has dismissed all entities that were acting on its behalf, there's no particular reason why such a call shouldn't be allowed to succeed.

I would view the ObjectDisposedException much like I view the collection-modified InvalidOperationException of IEnumerator<T>.MoveNext(): if some condition (either Dispose, or modification of a collection, respectively) would prevent a method from behaving "normally", the method is allowed to throw the indicated exception, and is not allowed to behave in some other erroneous fashion. On the other hand, if the method is capable of achieving its objectives without difficulty, and if doing so would make sense, such behavior should be considered just as acceptable as would be throwing an exception. In general, objects are not required to operate under such adverse conditions, but sometimes it may be helpful for them to do so [e.g. enumeration of a ConcurrentDictionary will not be invalidated by changes to the collection, since such invalidation would make concurrent enumeration useless].

supercat
  • 77,689
  • 9
  • 166
  • 211