How the MemoryStream declared in outer using statement is still available after inner using statement closes?

854 Views Asked by At

The Microsoft docs have the following piece of code on this page:

https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptostream?view=netframework-4.7

The most inner 'using' statement suppose to Dispose csEncrypt, which in it's turn suppose to Dispose the msEncrypt stream. However, right after the most inner using statement scope the msEncrypt is still alive and is used (the ToArray() of it is called).

The Microsoft document clearly states: "The StreamWriter object calls Dispose() on the provided Stream object when StreamWriter.Dispose is called.". The latter means that the csEncrypt is also disposed/closed, which in its turn closes the msEncrypt (https://referencesource.microsoft.com/#mscorlib/system/security/cryptography/cryptostream.cs,23052627697efb77, Can a CryptoStream leave the base Stream open?).

Then please explain how we can still call the "msEncrypt.ToArray();" after the end of scope of the innermost using statement?

using (MemoryStream msEncrypt = new MemoryStream())
{
    using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
    {
        using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
        {
            //Write all data to the stream.
            swEncrypt.Write(plainText);
        }

        encrypted = msEncrypt.ToArray();
    }
}
2

There are 2 best solutions below

0
Peter Duniho On BEST ANSWER

please explain how we can still call the "msEncrypt.ToArray();" after the end of scope of the innermost using statement?

Because the documentation promises us that that's so:

This method works when the MemoryStream is closed

(It's important to understand that in the context of Stream objects, the Close() and Dispose() methods are effectively synonymous.)

More generally, it's important to keep in mind that IDisposable.Dispose() has nothing at all to do with the lifetime of the object implementing that interface. The only thing it does is allow your code to inform the object when it is "done using it", to allow it to clean up (typically, freeing unmanaged resources…for any managed objects, there is no need because the CLR's garbage collector will take care of those).

Any object implementation is allowed to do whatever it feels appropriate when Dispose() is called. While it is typical that an object would become unusable after Dispose() is called, this is not required. And indeed, there are good reasons to allow at least some methods in MemoryStream, like ToArray(), to still be usable after the object has been disposed (but note that even for MemoryStream, most of the object's members are unusable after disposal…ToArray() is a special case).

In any case, calling Dispose() never will invalidate the object reference itself. The object reference will always remain valid as long as the object itself is reachable. It's up to the object itself to decide what should happen if any other code calls one of its members after it's been disposed. Most of the time, ObjectDisposedException will be thrown, but in some specific cases it makes sense to allow code to access members that are primarily useful only when the code is nearly done with the object and its main purpose has been been served. MemoryStream.ToArray() is such a member.

See possible duplicate questions:
Multiple using block, is this code safe?
Call to MemoryStream.GetBuffer() succeeds even after MemoryStream.Close(); Why?
Why can you still use a disposed object?
Clarify some things about IDisposable interface. Is instance (must be) equals null after calling Dispose?

Also see closely-related question:
CA2202, how to solve this case

10
Javier Silva Ortíz On

The Dispose method in these objects releases unmanaged resources, and it doesn't immediately destroy the entire object. Also, the garbage collector automatically releases the memory allocated to a managed object when that object is no longer used, but since you still use it outside the innermost using block, it won't be garbage collected yet. That's why you can still call the ToArray method, since it doesn't use any underlying unmanaged resources and the object hasn't been garbage collected yet. Also, it is not possible to predict when garbage collection will occur after an object is not used anymore.

If an object's Dispose method is called more than once, the object must ignore all calls after the first one. The object must not throw an exception if its Dispose method is called multiple times. Most people even recommend to suppress this warning. More info here. A refactored version (without exception handling) of your code could be like this:

MemoryStream msEncrypt = new MemoryStream();

CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write);

StreamWriter swEncrypt = new StreamWriter(csEncrypt)

//Write all data to the stream.
swEncrypt.Write(plainText);
encrypted = msEncrypt.ToArray();

//Do more work if needed.
//if you need to dispose of them manually and the current scope hasn't ended then just do:
swEncrypt.Dispose();

or if you want to maintain the using statements and add exception handling, then follow the recommended pattern here.