C# 为什么可以';收益率返回是否出现在带有捕获的try块中?

C# 为什么可以';收益率返回是否出现在带有捕获的try块中?,c#,exception,yield,C#,Exception,Yield,以下是可以的: try { Console.WriteLine("Before"); yield return 1; Console.WriteLine("After"); } finally { Console.WriteLine("Done"); } finally块在整个任务完成执行后运行(IEnumerator支持IDisposable以提供一种方法来确保这一点,即使枚举在完成之前被放弃)

以下是可以的:

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

当然,收益返回部分(目前)无法抛出任何东西,但这为什么会阻止我们使用封闭的
try
/
catch
来处理
收益返回之前或之后抛出的异常呢

更新:有一个-似乎他们在正确执行try/finally行为方面已经遇到了足够多的问题


编辑:此错误上的MSDN页为:。但是,这并不能解释为什么。

我推测,由于当您从枚举器返回时调用堆栈会被缠绕/解开,因此try/catch块实际上不可能“捕获”异常。(因为屈服返回块不在堆栈上,即使他发起了迭代块)


为了了解我要说的内容,请使用该迭代器设置迭代器块和foreach。检查foreach块内的调用堆栈外观,然后在迭代器try/finally块内检查它。

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

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

我已经遇到过一些类似的事情:

  • 属性不能是泛型的
  • X无法从X.Y(X中的嵌套类)派生
  • 迭代器块在生成的类中使用公共字段
在上述每种情况下,都有可能获得更多的自由度,代价是编译器的额外复杂性。团队做出了务实的选择,对此我表示赞赏——我宁愿使用一种限制性更强的语言,编译器的准确率达到99.9%(是的,有一些bug;前几天我遇到了一个bug),而不是一种更灵活、无法正确编译的语言

编辑:这是一个伪证据,证明了它是如何可行的

认为:

  • 您可以确保收益率返回部分本身不会抛出异常(预先计算值,然后您只需设置一个字段并返回“true”)
  • 允许在迭代器块中不使用yield-return的try/catch
  • 迭代器块中的所有局部变量都是生成类型中的实例变量,因此您可以自由地将代码移动到新方法
现在转换:

try
{
    Console.WriteLine("a");
    yield return 10;
    Console.WriteLine("b");
}
catch (Something e)
{
    Console.WriteLine("Catch block");
}
Console.WriteLine("Post");
转换为(某种伪代码):

唯一的重复是设置try/catch块,但这是编译器当然可以做到的


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

我已经接受了无敌飞碟的答案,直到微软的人来给这个想法泼冷水。但是我不同意意见部分的观点——当然正确的编译器比完整的编译器更重要,但是C#编译器已经非常聪明地为我们整理了这个转换。在这种情况下,更完整一点将使语言更易于使用、教授、解释,并且边缘案例或陷阱更少。所以我认为这是值得付出额外努力的。雷德蒙德的一些人搔了两周的头,因此在未来十年里,数百万的程序员可以放松一点

(我还怀有一种卑鄙的愿望,希望有一种方法,通过代码驱动迭代,使
产生返回
抛出一个“从外部”塞进状态机的异常。但我希望这样做的原因相当模糊。)

实际上,我对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");

<>但我确信这是可能的…

我熟悉C++中的堆栈展开,其中析构函数被调用到超出范围的局部对象。C#中对应的内容是try/finally。但当收益率回报发生时,这种平仓不会发生。对于try/catch,它不需要与yield-return交互mean@Radu094:不,我相信这是可能的。别忘了它已经可以最终处理了,这至少有点类似。我不认为我买这个。我认为这是完全可行的,但非常复杂。C#中的Try/catch块并不意味着可以重新进入。如果将它们拆分,则可以在异常后调用MoveNext(),并以可能无效的状态继续try块。你是谁并不重要
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");