为什么不能产生返回出现在一个尝试块捕捉?

以下是可以接受的:

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).

但这并不好:

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块中继续执行有什么问题?

当然,收益率返回部分(目前)不能抛出任何东西,但是为什么要阻止我们有一个封闭的 try/catch来处理在 yield return之前或之后抛出的异常?

更新: 有一个 Eric Lippert 的有趣评论-看起来他们在正确实现 try/finally 行为方面已经有足够多的问题了!

EDIT: The MSDN page on this error is: http://msdn.microsoft.com/en-us/library/cs1x15az.aspx. It doesn't explain why, though.

31551 次浏览

我推测,由于调用堆栈在您从枚举器返回返回值时受到伤害/解除伤害的方式,try/catch 块实际上不可能“捕获”异常。(因为产出返回块不在堆栈上,即使他创建了迭代块)

为了理解我所说的,使用迭代器设置迭代器块和 foreach。检查 foreach 块中的 Call Stack 是什么样子的,然后在迭代器 try/finally 块中检查它。

迭代器定义中的所有 yield语句都被转换为状态机中的一个状态,该状态机有效地使用 switch语句来推进状态。如果 是的在 try/catch 中为 yield语句生成代码,那么它必须在 try块中为 each yield语句复制 一切,同时为该块排除每个其他 yield语句。这并不总是可能的,特别是当一个 yield语句依赖于前一个 yield语句时。

我怀疑这是一个实用性问题,而不是可行性问题。我怀疑有非常非常少的时候这个限制是 事实上这个问题不能解决-但是编译器中增加的复杂性将是非常重要的。

我已经遇到了一些类似的情况:

  • 属性不能是泛型的
  • X 无法从 X.Y 派生(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.

编辑: 这里有一个伪证明为什么它是可行的。

Consider that:

  • 您可以确保屈服返回部分本身不会抛出异常(预先计算该值,然后只需设置一个字段并返回“ true”)
  • You're allowed try/catch which doesn't use yield return in an iterator block.
  • 迭代器块中的所有局部变量都是生成类型中的实例变量,因此您可以自由地将代码移动到新方法中

Now transform:

try
{
Console.WriteLine("a");
yield return 10;
Console.WriteLine("b");
}
catch (Something e)
{
Console.WriteLine("Catch block");
}
Console.WriteLine("Post");

转换成(某种伪代码) :

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");
}

唯一的重复是设置 try/catch 块——但是编译器肯定可以做到这一点。

我可能错过了一些东西-如果是这样,请让我知道!

我已经接受了《不可战胜的双簧管》的回答,直到有来自微软的人对这个想法泼冷水。但是我不同意这种观点——当然,一个正确的编译器比一个完整的编译器更重要,但是 C # 编译器已经非常聪明地为我们解决了这种转换。在这种情况下,多一点完整性可以使语言更容易使用、教学和解释,更少的边缘情况或陷阱。所以我认为这值得我们付出额外的努力。雷德蒙德的一些家伙挠了两个星期的头,结果在接下来的十年里,数以百万计的程序员可以稍微放松一下。

(我还怀有一种卑鄙的愿望,希望有一种方法可以让 yield return抛出一个异常,这个异常通过驱动迭代的代码“从外部”塞入状态机。但我希望这样做的理由却相当模糊。)

实际上,我对 Jon 的回答有一个疑问,那就是关于屈服返回表达式的抛出。

显然,收益率回报率10并没有那么糟糕,但这将是糟糕的:

yield return File.ReadAllText("c:\\missing.txt").Length;

因此,在前面的 try/catch 块中计算这个值不是更有意义吗:

case just_before_try_state:
try
{
Console.WriteLine("a");
__current = File.ReadAllText("c:\\missing.txt").Length;
}
catch (Something e)
{
CatchBlock();
goto case post;
}
return true;

下一个问题是嵌套的 try/catch 块和重新抛出的异常:

try
{
Console.WriteLine("x");


try
{
Console.WriteLine("a");
yield return 10;
Console.WriteLine("b");
}
catch (Something e)
{
Console.WriteLine("y");


if ((DateTime.Now.Second % 2) == 0)
throw;
}
}
catch (Something e)
{
Console.WriteLine("Catch block");
}
Console.WriteLine("Post");

但我相信有可能..。