1

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();
    }
}
Tiger Galo
  • 289
  • 4
  • 15
  • The most inner `using` does not dispose the `csEncrypt` variable, but instead the `swEncrypt` variable, which is holding the `StreamWriter` object. The variable `csEncrypt` is free to use (inside the corresponding `using` block) as well as `msEncrypt`. – Progman Nov 07 '19 at 19:05
  • Then why the compiler gives a warning CA2202 saying that csEncrypt and msEncrypt can be disposed more than once? – Tiger Galo Nov 07 '19 at 19:26
  • 1
    Please [edit] your question to include the full source code you have (as a [mcve] if possible) and the complete error message with all the available information about the warning you get from the compiler. – Progman Nov 07 '19 at 19:32
  • Btw.: you might want to look at https://stackoverflow.com/questions/3831676/ca2202-how-to-solve-this-case – Progman Nov 07 '19 at 19:33
  • Related: [Does disposing a StreamWriter close the underlying stream?](https://stackoverflow.com/a/1187727/2791540) – John Wu Nov 07 '19 at 19:52
  • Your question is unclear. Are you asking about the CA2202 warning? Are you asking about if/when particular objects are actually disposed? Are you asking about the actual object _lifetime_, i.e. as it's known to the garbage collector? Between the text of your question and your comments, there are numerous ways to interpret the question. Please improve it so that it is focused on _one specific question_, and so that it's perfectly clear what that one specific question is. – Peter Duniho Nov 07 '19 at 23:13
  • @Peter Duniho, I edited my question, hope it's more clear now. This is the actual question, but further discussion also touched the topic about the warning CA2202 around it, which is related and feasible I guess. – Tiger Galo Nov 12 '19 at 17:09
  • Possible duplicate of [Multiple using block, is this code safe?](https://stackoverflow.com/questions/39145436/multiple-using-block-is-this-code-safe/39145504#39145504) – Peter Duniho Nov 12 '19 at 17:54
  • Possible duplicate of [Call to MemoryStream.GetBuffer() succeeds even after MemoryStream.Close(); Why?](https://stackoverflow.com/questions/23865339/call-to-memorystream-getbuffer-succeeds-even-after-memorystream-close-why/23865503#23865503) – Peter Duniho Nov 12 '19 at 17:54
  • Possible duplicate of [Why can you still use a disposed object?](https://stackoverflow.com/questions/35825309/why-can-you-still-use-a-disposed-object/35825344#35825344) – Peter Duniho Nov 12 '19 at 17:55
  • Possible duplicate of [Clarify some things about IDisposable interface. Is instance (must be) equals null after calling Dispose?](https://stackoverflow.com/questions/3649573/clarify-some-things-about-idisposable-interface-is-instance-must-be-equals-nu/3649594#3649594) – Peter Duniho Nov 12 '19 at 17:55
  • See also closely-related [CA2202, how to solve this case](https://stackoverflow.com/questions/3831676/ca2202-how-to-solve-this-case/3831911#3831911) – Peter Duniho Nov 12 '19 at 17:56
  • Thanks for all the links you shared. Thanks to everyone. – Tiger Galo Nov 12 '19 at 18:19

2 Answers2

1

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.

Javier Silva Ortíz
  • 2,864
  • 1
  • 12
  • 21
  • Then why the compiler gives a warning CA2202 saying that csEncrypt and msEncrypt can be disposed more than once? – Tiger Galo Nov 07 '19 at 19:26
  • The compiler **does not** generate this type of warnings. It's VS Code analysis that does. – Javier Silva Ortíz Nov 07 '19 at 19:32
  • Is there any way to refactor this code then to avoid those warnings other than suppressing them? – Tiger Galo Nov 07 '19 at 19:37
  • I totally agree with @Javier, but still confused, if the innermost using doesn't dispose the outer using declarations then why would it call Dispose multiple times for the outer ones? – Tiger Galo Nov 07 '19 at 19:47
  • The warning said, "... *can* ...". The link I provided is just about this. – Javier Silva Ortíz Nov 07 '19 at 19:52
  • For completeness though the above code should be wrapped by try-catch-finally to handle possible exceptions and make sure all xxEncrypt instances are dispose safely in the finally block. – Tiger Galo Nov 07 '19 at 20:18
  • Yes, sorry, I had the edit ready but switched tabs and wandered off, that disposable pattern is there in the last link. – Javier Silva Ortíz Nov 07 '19 at 20:21
  • With regards to the pattern link in the answer it's applicable if one doesn't need to use the 'stream' after the scope of the "using (StreamWriter writer = new StreamWriter(stream))" statement block, since the 'stream' is assigned null in that block. – Tiger Galo Nov 07 '19 at 20:25
  • It's applicable, just keep in mind it's a *pattern*. Not *copy-tasta*. – Javier Silva Ortíz Nov 07 '19 at 20:29
  • Just edited it now to explicitly address your question. Hope is much more clear now. Happy coding! – Javier Silva Ortíz Nov 07 '19 at 20:48
1

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

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136