Reputation: 2032
If I have a coroutine as follows, will the code in the finally block get called?
public IEnumerator MyCoroutine(int input)
{
try
{
if(input > 10)
{
Console.WriteLine("Can't count that high.");
yield break;
}
Console.WriteLine("Counting:");
for(int i = 0; i < input; i++)
{
Console.WriteLine(i.ToString());
yield return null;
}
}
finally
{
Console.WriteLine("Finally!");
}
}
Upvotes: 12
Views: 7276
Reputation: 27339
According to the documentation, yes, code in the finally will always be called.
Since you are using yield, the finally block will not be executed until you access the IEnumerator returned by the method. For example:
void Main()
{
var x = MyCoroutine(12);
//Console.WriteLines will happen when the following
//statement is executed
var y = x.MoveNext();
}
Upvotes: 1
Reputation: 3314
As long as the iterator/enumerator is disposed properly (IDisposable.Dispose()
is called) then yes:
Control is always passed to the finally block regardless of how the try block exits.
http://msdn.microsoft.com/en-us/library/zwc8s4fz.aspx
However, be careful that Dispose()
is indeed called, or else you may end up with unintended results at best. For more information on this phenomenon and some gotchas to watch out for, check out this blog post:
Yield and usings - your Dispose may not be called!
(Thanks to Scott B for providing the link, placing in the answer since everybody seems to be missing it)
Additionally:
A yield return statement cannot be located anywhere inside a try-catch block. It can be located in a try block if the try block is followed by a finally block.
http://msdn.microsoft.com/en-us/library/9k7k7cf0.aspx
Upvotes: 25
Reputation: 81179
All of the answers so far omit a crucial detail: code in a finally
block which wraps a yield return
will execute if and when IDisposable.Dispose
is called upon the iterator/enumerator which executed the yield return
. If outside code calls GetEnumerator()
on an iterator and then, calls MoveNext()
until the iterator performs the yield return
within a finally
block, and the outside code then abandons the enumerator without calling Dispose
, the code in the finally
block will not run. Depending upon what the iterator was doing, it may get annihilated by the garbage collector (though without having a chance at cleaning up any outside resources) or it may end up permanently or semi-permanently rooted as a memory leak (that could happen if, for example, it attached a lambda expression to a long-lived object's event handler).
Note that while both vb and c# are very good about ensuring that foreach
loops will call Dispose
on enumerators, it's possible to use iterators by calling GetEnumerator()
explicitly, and it's possible some code might do so without calling Dispose()
. There isn't much an iterator can do about that, but anyone writing iterators needs to be aware of the possibility.
Upvotes: 20
Reputation: 301177
If you were just lazy to add Main()
etc, get the code from here, run it and see what happens:
Response to @Henk Holterman's comment
Any of the below four is valid:
* IEnumerable
* IEnumerable<T>
* IEnumerator
* IEnumerator<T>
Upvotes: 2