Reputation: 631
I understand that enumerators and the yield keyword can be used to help with async/staggered operations, as you can call MoveNext()
to run the next block of code.
However, I don't really understand what that Enumerator object is. Where does the memory in use of the scope of the Enumerator go? If you don't MoveNext()
an Enumerator all the way, does it get GC'd eventually?
Basically, I'm trying to keep my GC hits down, as I am potentially using a LOT of Enumerators and GC can be an issue inside Unity, especially due to the older version of Mono it uses.
I have tried to profile this but can't wrap my head around them still. I don't understand the scoping/referencing that happens with Enumerators. I also don't understand if Enumerators are created as Objects when you create one from a function that yields.
The following example shows my confusion better:
// Example enumerator
IEnumerator<bool> ExampleFunction()
{
SomeClass heavyObject = new SomeClass();
while(heavyObject.Process())
{
yield return true;
}
if(!heavyObject.Success)
{
yield return false;
}
// In this example, we'll never get here - what happens to the incomplete Enumerator
// When does heavyObject get GC'd?
heavyObject.DoSomeMoreStuff();
}
// example call - Where does this enumerator come from?
// Is something creating it with the new keyword in the background?
IEnumerator<bool> enumerator = ExampleFunction();
while(enumerator.MoveNext())
{
if(!enumerator.Current)
{
break;
}
}
// if enumerator is never used after this, does it get destroyed when the scope ends, or is it GC'd at a later date?
Upvotes: 9
Views: 2300
Reputation: 171206
You probably should read an enumerator internals post. From the internals you can answer all these questions.
A crash course: Each iterator method execution returns a new enumerator object. Local variables become fields.
If nobody uses that enumerator object anymore it is eligible for collection. Also, all references that local variables were creating go away for GC purposes.
There are some edge cases for when exactly local variables stop being GC references. If your enumerators are fairly short lived this does not matter much.
The CLR does not know what an enumerator is. It's just a class generated by the C# compiler.
Pulling the link by xanatos into this answer since it's illustrative and he does not seem to post an answer: http://goo.gl/fs4eNo
Upvotes: 6
Reputation: 37050
The enumerator is managed the same way as every other managed instance - when it is out of scope. Thus if MoveNext()
is never called the GC deletes (or better marks it for deletion) the enumerator when it is out of its scope. Iterating an enumerator does not happen in any parallel so both execution and garbage-collection run deterministically and sequentually when you´re done with it.
When inside an iterator-method the yield-return
-stament is not ment to be the very last statement at all, it simply means that when MoveNext()
is called the current result should be returned. However everything behind yield return
runs after the call to MoveNext
so also your call to DoSomeMoreStuff
.
Your heavyObject
however has the scope of the method, thus it lives as long as the iterator - where the iterator is the block inside your while
-loop. This means that if your iterator does not even return any instance heavyObject
will be immediately disposed, otherwise when iteration has been finished.
Upvotes: 2
Reputation: 47560
// if enumerator is never used after this, does it get destroyed when the scope ends
Correct
Eligibility for collecting is having no references. For GC's scoping there is nothing special with enumerators. Enumerators disposed/cleared in the same way when they go out of scope as any other type.
Upvotes: 3