Lets say I have an infinite state machine to generate random md5 hashes:
public static IEnumerable<string> GetHashes()
{
using (var hash = System.Security.Cryptography.MD5.Create())
{
while (true)
yield return hash.ComputeHash(Guid.NewGuid().ToByteArray());
}
}
In the above example I use an using
statement. Will the .Dispose()
method ever be called? CQ, will the unmanaged resources ever be freed?
For example, if I use the machine as follows:
public static void Test()
{
int counter = 0;
var hashes = GetHashes();
foreach(var md5 in hashes)
{
Console.WriteLine(md5);
counter++;
if (counter > 10)
break;
}
}
Since the hashes
variable will go out of scope (and I presume garbage collected) will the dispose method be called to free the resources used by System.Security.Cryptography.MD5
or is this a memory leak?
Largely, it depends on how you code it. But in your example,
Dispose
will be called.Here's an explanation on how iterators get compiled.
And specifically, talking about
finally
:...
Let's change your original code blocks a bit to boil it down to the essentials, while still keeping it interesting enough to analyze. This is not exactly equivalent to what you posted, but we're still using the value of the iterator.
This will print the numbers from 1 to 10 before printing
Disposed!
What actually happens under the covers? A whole lot more. Let's tackle the outer layer first,
UseEnumerable
. Theforeach
is syntactic sugar for the following:For the exact details (because even this is simplified, a little) I refer you to the C# language specification, section 8.8.4. The important bit here is that a
foreach
entails an implicit call to theDispose
of the enumerator.Next, the
using
statement inCreateEnumerable
is syntactic sugar as well. In fact, let's write out the whole thing in primitive statements so we can make more sense of the translation later:The exact rules for implementation of iterator blocks are detailed in section 10.14 of the language specification. They're given in terms of abstract operations, not code. A good discussion on what kind of code is generated by the C# compiler and what each part does is given in C# in Depth, but I'm going to give a simple translation instead that still complies with the specification. To reiterate, this is not what the compiler will actually produce, but it's a good enough approximation to illustrate what's happening and leaves out the more hairy bits that deal with threading and optimization.
The essential bit here is that the code block is split up at the occurrences of a
yield return
oryield break
statement, with the iterator responsible for remembering "where we were" at the time of the interruption. Anyfinally
blocks in the body are deferred until theDispose
. The infinite loop in your code is really not an infinite loop anymore, because it's interrupted by periodicyield return
statements. Note that, because thefinally
block isn't actually afinally
block anymore, it getting executed is a little less certain when you're dealing with iterators. This is why usingforeach
(or any other way that ensures theDispose
method of the iterator is called in afinally
block) is essential.This is a simplified example; things get much more interesting when you make the loop more complex, introduce exceptions, etcetera. The burden of "just making this work" is on the compiler.