C# 为什么 yield return 不能出现在带有 catch 的 try 块中?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/346365/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 00:00:13  来源:igfitidea点击:

Why can't yield return appear inside a try block with a catch?

c#exceptionyield-keyword

提问by Daniel Earwicker

The following is okay:

以下是可以的:

try
{
    Console.WriteLine("Before");

    yield return 1;

    Console.WriteLine("After");
}
finally
{
    Console.WriteLine("Done");
}

The finallyblock runs when the whole thing has finished executing (IEnumerator<T>supports IDisposableto provide a way to ensure this even when the enumeration is abandoned before it finishes).

finally块在整个事情完成执行时运行(IEnumerator<T>支持IDisposable提供一种方法来确保这一点,即使枚举在完成之前被放弃)。

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 WriteLinecalls inside the try block. What's the problem with continuing the execution in catchblock?

假设(为了论证)WriteLinetry 块内的一个或另一个调用引发了异常。在catch块中继续执行有什么问题?

Of course, the yield return part is (currently) unable to throw anything, but why should that stop us from having an enclosing try/catchto deal with exceptions thrown before or after a yield return?

当然,yield return 部分(目前)无法抛出任何东西,但是为什么要阻止我们使用封闭的try/catch来处理在 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!

更新:这里有一个来自 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.

编辑:有关此错误的 MSDN 页面是:http: //msdn.microsoft.com/en-us/library/cs1x15az.aspx。不过,它并没有解释为什么。

采纳答案by Jon Skeet

I suspect this is a matter of practicality rather than feasibility. I suspect there are very, very few times where this restriction is actuallyan 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
  • 属性不能是通用的
  • X 无法从 XY 派生(X 中的嵌套类)
  • 在生成的类中使用公共字段的迭代器块

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.

在每一种情况下,都有可能获得更多的自由,但代价是编译器的额外复杂性。团队做出了务实的选择,为此我为他们鼓掌 - 我宁愿拥有一种具有 99.9% 准确编译器的稍微限制性的语言(是的,存在错误;我前几天在 SO 上遇到了一个)而不是更多无法正确编译的灵活语言。

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
  • 您可以确保 yield return 部分本身不会引发异常(预先计算值,然后您只需设置一个字段并返回“true”)
  • 您可以在迭代器块中使用不使用 yield return 的 try/catch。
  • 迭代器块中的所有局部变量都是生成类型中的实例变量,因此您可以自由地将代码移动到新方法中

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.

唯一的重复是设置 try/catch 块 - 但这是编译器当然可以做的事情。

I may well have missed something here - if so, please let me know!

我很可能在这里错过了一些东西 - 如果是这样,请告诉我!

回答by Radu094

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)

我推测,由于当您从枚举器产生返回时调用堆栈被缠绕/解开的方式,try/catch 块不可能真正“捕获”异常。(因为 yield return 块不在堆栈上,即使他发起了迭代块)

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.

要了解我正在谈论的内容,请使用该迭代器设置一个迭代器块和一个 foreach。检查调用堆栈在 foreach 块内的样子,然后在迭代器 try/finally 块内检查它。

回答by Mark Cidade

All the yieldstatements in an iterator definition are converted to a state in a state machine which effectively uses a switchstatement to advance states. If it didgenerate code for yieldstatements in a try/catch it would have to duplicate everythingin the tryblock for eachyieldstatement while excluding every other yieldstatement for that block. This isn't always possible, particularly if one yieldstatement is dependant on an earlier one.

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

回答by Daniel Earwicker

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.

我已经接受了 The Invincible SkeET 的回答,直到 Microsoft 的某个人过来为这个想法泼冷水。但我不同意意见问题部分——当然,正确的编译器比完整的编译器更重要,但 C# 编译器已经非常聪明地为我们整理了这种转换。在这种情况下,稍微完整一点将使语言更易于使用、教授、解释,并且会减少边缘情况或陷阱。所以我认为付出额外的努力是值得的。雷德蒙德的一些人用了两周的时间挠头,结果是未来十年数百万的程序员可以放松一点。

(I also harbour a sordid desire for there to be a way to make yield returnthrow 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.)

(我也怀有一种肮脏的愿望,希望有一种方法可以yield return通过驱动迭代的代码“从外部”抛出一个已塞入状态机的异常。但我想要这个的原因很模糊。)

Actually one query I have about Jon's answer is to do with the yield return expression throwing.

实际上,我对 Jon 的回答的一个疑问是与抛出的 yield return 表达式有关。

Obviously yield return 10 isn't so bad. But this would be bad:

显然,yield return 10 还不错。但这会很糟糕:

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

So wouldn't it make more sense to evaluate this inside the preceeding try/catch block:

所以在前面的 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;

The next problem would be nested try/catch blocks and rethrown exceptions:

下一个问题是嵌套的 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");

But I'm sure it's possible...

但我确定这是可能的......