Liron
Liron

Reputation: 2032

c# yield and try-finally

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

Answers (4)

rsbarro
rsbarro

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

Chris Barlow
Chris Barlow

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

supercat
supercat

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

manojlds
manojlds

Reputation: 301177

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>

Upvotes: 2

Related Questions