为什么yield return 不能出现在带有catch 的try 块内?
下面的情况是可以的:
try
{
Console.WriteLine("Before");
yield return 1;
Console.WriteLine("After");
}
finally
{
Console.WriteLine("Done");
}
finally
块在整个事情完成执行时运行(IEnumerator
支持 IDisposable
以提供一种方法来确保即使枚举在完成之前被放弃也是如此)。
但这是不行的:
try
{
Console.WriteLine("Before");
yield return 1; // error CS1626: Cannot yield a value in the body of a try block with a catch clause
Console.WriteLine("After");
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
假设(为了论证)try 块内的一个或另一个 WriteLine
调用引发了异常。 在catch
块中继续执行有什么问题?
当然,yield 返回部分(当前)无法抛出任何内容,但是为什么这会阻止我们使用封闭的 try
/catch
来处理之前抛出的异常或在收益回报
之后?
更新:有一个Eric Lippert 的有趣评论 - 似乎他们在正确实现 try/finally 行为方面已经遇到了足够多的问题!
编辑:有关此错误的 MSDN 页面为: http://msdn.microsoft.com /en-us/library/cs1x15az.aspx。 但它没有解释为什么。
The following is okay:
try
{
Console.WriteLine("Before");
yield return 1;
Console.WriteLine("After");
}
finally
{
Console.WriteLine("Done");
}
The finally
block runs when the whole thing has finished executing (IEnumerator<T>
supports IDisposable
to provide a way to ensure this even when the enumeration is abandoned before it finishes).
But this is not okay:
try
{
Console.WriteLine("Before");
yield return 1; // error CS1626: Cannot yield a value in the body of a try block with a catch clause
Console.WriteLine("After");
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
Suppose (for the sake of argument) that an exception is thrown by one or other of the WriteLine
calls inside the try block. What's the problem with continuing the execution in catch
block?
Of course, the yield return part is (currently) unable to throw anything, but why should that stop us from having an enclosing try
/catch
to deal with exceptions thrown before or after a yield return
?
Update: There's an interesting comment from Eric Lippert here - seems that they already have enough problems implementing the try/finally behaviour correctly!
EDIT: The MSDN page on this error is: http://msdn.microsoft.com/en-us/library/cs1x15az.aspx. It doesn't explain why, though.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
我怀疑这是一个实用性问题而不是可行性问题。 我怀疑在非常非常少的情况下,这个限制实际上是一个无法解决的问题 - 但编译器中增加的复杂性将非常显着。
我已经遇到过一些类似的事情:
在每种情况下有可能获得更多的自由,但代价是编译器的额外复杂性。 该团队做出了务实的选择,对此我为他们鼓掌 - 我宁愿使用一种稍微严格一些的语言,并且具有 99.9% 准确的编译器(是的,存在错误;前几天我在 SO 上遇到了一个),而不是更多无法正确编译的灵活语言。
编辑:这是一个伪证明,说明它为何可行。
考虑一下:
现在将:转换
为(某种伪代码):
唯一的重复是设置 try/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:
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:
Now transform:
into (sort of pseudo-code):
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!
迭代器定义中的所有
yield
语句都会转换为状态机中的状态,该状态机有效地使用switch
语句来推进状态。 如果它确实在try/catch中为yield
语句生成代码,那么它必须复制try
中的所有内容 每个yield
语句的块,同时排除该块的所有其他yield
语句。 这并不总是可行,特别是当一个yield
语句依赖于前一个语句时。All the
yield
statements in an iterator definition are converted to a state in a state machine which effectively uses aswitch
statement to advance states. If it did generate code foryield
statements in a try/catch it would have to duplicate everything in thetry
block for eachyield
statement while excluding every otheryield
statement for that block. This isn't always possible, particularly if oneyield
statement is dependant on an earlier one.我已经接受了无敌飞碟的答案,直到微软的人来给这个想法泼冷水。 但我不同意意见问题部分 - 当然,一个正确的编译器比一个完整的编译器更重要,但 C# 编译器已经非常聪明地为我们解决了这种转换问题。 在这种情况下,多一点完整性将使该语言更易于使用、教学、解释,并减少边缘情况或陷阱。 所以我认为额外的努力是值得的。 雷德蒙德的几个人摸索了两周的时间,结果在接下来的十年里数百万程序员可以放松一点。
(我还怀有一种肮脏的愿望,希望有一种方法可以让
yield return
抛出一个异常,该异常已通过驱动迭代的代码“从外部”填充到状态机中。但是我的想要这个的原因相当模糊。)实际上,我对乔恩的答案的一个疑问是与yield返回表达式抛出有关。
显然,yield return 10 并没有那么糟糕。 但这会很糟糕:
所以在前面的 try/catch 块内评估它不是更有意义吗:
下一个问题将是嵌套的 try/catch 块并重新抛出异常:
但我确信这是可能的......
I've accepted THE INVINCIBLE SKEET's answer until someone from Microsoft comes along to pour cold water on the idea. But I don't agree with the matter-of-opinion part - of course a correct compiler is more important than a complete one, but the C# compiler is already very clever in sorting out this transformation for us as far as it does. A little more completeness in this case would make the language easier to use, teach, explain, with fewer edge cases or gotchas. So I think it would be worth the extra effort. A few guys in Redmond scratch their heads for a fortnight, and as a result millions of coders over the next decade can relax a little more.
(I also harbour a sordid desire for there to be a way to make
yield return
throw an exception that has been stuffed into the state machine "from the outside", by the code driving the iteration. But my reasons for wanting this are quite obscure.)Actually one query I have about Jon's answer is to do with the yield return expression throwing.
Obviously yield return 10 isn't so bad. But this would be bad:
So wouldn't it make more sense to evaluate this inside the preceeding try/catch block:
The next problem would be nested try/catch blocks and rethrown exceptions:
But I'm sure it's possible...
我推测,由于当您从枚举器产生返回时调用堆栈缠绕/展开的方式,try/catch 块不可能实际“捕获”异常。 (因为yield return块不在堆栈上,即使他发起了迭代块)
为了了解我正在谈论的内容,设置一个迭代器块和一个使用该迭代器的foreach。 检查 foreach 块内的调用堆栈是什么样子,然后在迭代器 try/finally 块内检查它。
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.