Why can't yield return appear inside a try block with a catch?
I suspect this is a matter of practicality rather than feasibility. I suspect there are very, very few times where this restriction is actually an issue that can't be worked around - but the added complexity in the compiler would be very significant.
There are a few things like this that I've already encountered:
- Attributes not being able to be generic
- Inability for X to derive from X.Y (a nested class in X)
- Iterator blocks using public fields in the generated classes
In each of these cases it would be possible to gain a little bit more freedom, at the cost of extra complexity in the compiler. The team made the pragmatic choice, for which I applaud them - I'd rather have a slightly more restrictive language with a 99.9% accurate compiler (yes, there are bugs; I ran into one on SO just the other day) than a more flexible language which couldn't compile correctly.
EDIT: Here's a pseudo-proof of how it why it's feasible.
Consider that:
- You can make sure that the yield return part itself doesn't throw an exception (precalculate the value, and then you're just setting a field and returning "true")
- You're allowed try/catch which doesn't use yield return in an iterator block.
- All local variables in the iterator block are instance variables in the generated type, so you can freely move code to new methods
Now transform:
try
{
Console.WriteLine("a");
yield return 10;
Console.WriteLine("b");
}
catch (Something e)
{
Console.WriteLine("Catch block");
}
Console.WriteLine("Post");
into (sort of pseudo-code):
case just_before_try_state:
try
{
Console.WriteLine("a");
}
catch (Something e)
{
CatchBlock();
goto case post;
}
__current = 10;
return true;
case just_after_yield_return:
try
{
Console.WriteLine("b");
}
catch (Something e)
{
CatchBlock();
}
goto case post;
case post;
Console.WriteLine("Post");
void CatchBlock()
{
Console.WriteLine("Catch block");
}
The only duplication is in setting up try/catch blocks - but that's something the compiler can certainly do.
I may well have missed something here - if so, please let me know!
All the yield
statements in an iterator definition are converted to a state in a state machine which effectively uses a switch
statement to advance states. If it did generate code for yield
statements in a try/catch it would have to duplicate everything in the try
block for each yield
statement while excluding every other yield
statement for that block. This isn't always possible, particularly if one yield
statement is dependant on an earlier one.
I would speculate that because of the way the call stack gets wound/unwound when you yield return from an enumerator it becomes impossible for a try/catch block to actually "catch" the exception. (because the yield return block is not on the stack, even though he originated the iteration block)
To get an ideea of what I'm talking about setup an iterator block and a foreach using that iterator. Check what the Call Stack looks like inside the foreach block and then check it inside the iterator try/finally block.