try/finally is allowed around a yield return, and it does what you would hope — even when
the consumer walks away early.
static IEnumerable<string> Lines()
{
Console.WriteLine("opening");
try
{
yield return "first";
yield return "second";
yield return "third";
}
finally
{
Console.WriteLine("closing");
}
}
static void Main()
{
foreach (var line in Lines())
{
Console.WriteLine(line);
if (line == "second") break;
}
}
Output
opening first second closing
The loop broke after the second value, and closing still printed.
That works because the generated state machine implements IDisposable, and foreach compiles to
a try/finally that calls Dispose() on the enumerator. Disposing an iterator that is parked
inside a try runs its finally blocks.
The case where cleanup is skipped
This only holds if the consumer disposes the enumerator — which foreach always does. If you drive
MoveNext() by hand and simply stop, nothing disposes it and the finally never runs. Wrap manual
enumeration in using, or use foreach.
This is why File.ReadLines can hand you a lazy sequence and still close the file when you stop
reading. The using sits inside the iterator, and your break disposes it.
Try it: Task 1
Open the editor