c# yield and try-finally
Asked Answered
M

4

12

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!");
  }
}
Mingle answered 25/5, 2011 at 18:39 Comment(6)
Did you try it and see what happens?Unconsidered
I guess your console will hold the answer to that question.Horatio
This can easily be verified in your debuggerSleek
Didn't you mean IEnumerable<int> as return type?Bittner
@Henk Holterman - I have added a response in my answerTerrilyn
That's not so easily verified just by trying and see what happens. The finally block will be executed if the Enumerator is disposed, but it won't otherwise. If you don't know that you need call Dispose on the enumerator (or use it in a using statement) the finally block will not be called and you will be left with the impression that finally blocks are not executed in those cases.Clavicembalo
C
25

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

Catch answered 25/5, 2011 at 18:43 Comment(1)
That is an excellent blog post! Very clear on when and how it all works.Catch
B
20

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.

Butyraceous answered 6/12, 2011 at 21:57 Comment(1)
s/emit/omit/ - confused me when reading this excellent answer for the first time, +1.Petulant
T
2

If you were just lazy to add Main() etc, get the code from here, run it and see what happens:

YieldReturnAndFinally

Response to @Henk Holterman's comment

Any of the below four is valid:

* IEnumerable
* IEnumerable<T>
* IEnumerator
* IEnumerator<T>
Terrilyn answered 25/5, 2011 at 18:47 Comment(0)
F
1

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();
}
Fardel answered 25/5, 2011 at 18:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.