The implementation of iterators in C# and its consequences (part 3)


I mentioned that there was an exception to the general statement that the conversion of an iterator into traditional C# code is something you could have done yourself. That's true, and it was also a pun, because the exception is exception handling.

If you have a try ... finally block in your iterator, the language executes the finally block under the following conditions:

  • After the last statement of the try block is executed. (No surprise here.)

  • When an exception propagates out of the try block. (No surprise here either.)

  • When execution leaves the try block via yield break.

  • When the iterator is Disposed and the iterator body was trapped inside a try block at the time.

That last case can occur if somebody decides to abandon the enumerator before it is finished.

IEnumerable<int> CountTo10()
{
 try {
  for (int i = 1; i <= 10; i++) {
   yield return i;
  }
 } finally {
  System.Console.WriteLine("finally");
 }
}

foreach (int i in CountTo10()) {
 System.Console.WriteLine(i);
 if (i == 5) break;
}

This code fragment prints "1 2 3 4 5 finally".

If you think about it, this behavior is completely natural. You want the finally block to execute when the try block is finished executing, either by normal or abnormal means. Although control leaves the try block during the yield return, it comes back when the caller asks for the next item from the enumerator, so execution of the try block isn't finished yet. The try is finished executing after the last statement completes, an exception is thrown past it, or execution is abandoned when the enumerator is prematurely destroyed.

And this is exactly what you want when you use the finally block to clean up resources used by the try block.

Now, technically, you can write this yourself without using iterators, but it's pretty ugly. You'll need more internal state variables to keep track of whether the try block is still active and whether the exit of the try block is temporary (due to yield return) or permanent. It's a real pain in the neck, however, so you probably are better off letting the compiler do the work for you.

Comments (10)
  1. Professor Farnsworth says:

    Good news everyone!  CLR week is almost over!

  2. Bob says:

    I want more CLR week! GIME GIME GIME. :)

  3. Eric Lippert says:

    It is, as you note, a hard problem, so hard that we on the compiler team have gotten it wrong numerous times. I apologize for the inconvenience.

    There are scenarios where you can have multiple nested try blocks and the released v3 compiler generates code that runs the finally blocks outside-to-inside instead of the correct order.

    I believe we have fixed those bugs for the v3 service release, but we have found more since then.  

    (So far the only remaining bugs I know of involve bizarre cases of, say, a yield break which branches out to a finally which then executes a second yield break. The control flow can get messed up in complicated circumstances like that.)

    If anyone finds bugs involving iterators blocks and finally execution, please email me via my blog.

  4. Daniel Plaisted says:

    Was support for try blocks with yield statements inside them added in VS2008?  I tried to do this in VS2005 and the compiler told me that it wasn’t supported, so I had to work around it.  It was kind of a pain.

  5. Daniel Plaisted says:

    Regarding my previous comment, it looks like the limitation I was remembering was that you cannot yield a value in the body of a try block with a catch clause.  You also can’t yield a value within a catch clause.

    I assume there are good reasons for this but for my situation it would have been nice if it worked :)

  6. Jack Mathews says:

    If you don’t Dispose the iterator, will the finalizer cause the code in the finally blocks to get executed?

  7. My latest in a series of the weekly, or more often, summary of interesting links I come across related to Visual Studio. Greg Duncan posted a link to the release announcement for Task Board for Team System Beta 2 . Raymond Chen discussed the implementation

  8. The Old New Thing : The implementation of iterators in C# and its consequences (part 3) Notes from a

Comments are closed.