为什么不能在一个try块中出现返回值?

以下是好的:

try { Console.WriteLine("Before"); yield return 1; Console.WriteLine("After"); } finally { Console.WriteLine("Done"); } 

finally块在整个执行完成时运行( IEnumerator<T>支持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调用抛出exception。 在catch块中继续执行有什么问题?

当然,yield return部分(当前)不能抛出任何东西,但是为什么我们不应该封闭try / catch来处理在yield return之前或之后抛出的exception呢?

更新: Eric Lippert在这里有一个有趣的评论 – 似乎他们已经有足够的问题正确地执行try / finally行为!

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

我怀疑这是一个实际问题,而不是可行性问题。 我怀疑这个限制实际上是一个无法解决的问题,但是在编译器中增加的复杂性是非常重要的。

有几件这样的事情我已经遇到了:

  • 属性不能是通用的
  • X不能从XY派生(X中的嵌套类)
  • 迭代器在生成的类中使用公共字段

在每一种情况下,都有可能获得更多的自由度,但这是以编译器额外的复杂性为代价的。 团队做出了实用的select,为此我赞赏他们 – 我宁愿有一个99.9%准确的编译器(是的,有错误,我在前一天遇到了一个SO),而不是更多的限制性语言灵活的语言无法正确编译。

编辑:这是一个如何为什么可行的伪certificate。

考虑到:

  • 您可以确保yield return部分本身不会抛出exception(预先计算值,然后您只需设置一个字段并返回“true”)
  • 你可以使用try / catch,它不会在迭代器块中使用yield return。
  • 迭代器块中的所有局部variables都是生成types的实例variables,因此您可以自由地将代码移动到新的方法中

现在转变:

 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块 – 但这是编译器当然可以做的事情。

我可能错过了这里的东西 – 如果是这样,请让我知道!

迭代器定义中的所有yield语句都转换为状态机中的一个状态,该状态机有效地使用switch语句来提前状态。 如果它在try / catch中为yield语句生成了代码,它将不得不在try语句块中为每个 yield语句复制所有内容 ,同时排除该语句块的所有yield语句。 这并不总是可能的,特别是如果一个yield声明依赖于较早的yield声明。

我会推测,由于调用堆栈在从枚举器返回时遇到/解除的方式,try / catch块实际上“捕获”exception变得不可能。 (因为产量返回块不在堆栈上,即使他产生了迭代块)

为了得到我正在谈论的设置迭代器块和使用该迭代器的foreach的ideea。 检查“调用堆栈”在foreach块中的内容,然后在迭代器try / finally块中检查它。

我已经接受了无敌的SKEET的答案,直到微软的某个人来为这个想法泼冷水。 但是我不同意这个观点 – 当然,一个正确的编译器比一个完整的编译器更重要,但是C#编译器已经非常聪明地将这个转换整理到了我们这里。 在这种情况下,更完整一点就会使语言更容易使用,教导,解释,边缘案例或陷阱更less。 所以我认为这是值得的额外的努力。 雷德蒙德的几个人在头两个星期的时间里头痛,结果在接下来的十年中,数百万的编码者可以放松一点。

(我也怀有一个肮脏的愿望,那就是要有一个方法来让yield return抛出一个例外,这个例外已经被外部的“从外部”塞进了状态机,由代码驱动迭代。但是我想要这个的原因很模糊。)

其实我有一个关于Jon的回答的查询是关于yield returnexpression式抛出。

显然收益率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块和重新抛出的exception:

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

但我相信这是可能的…

对于那些使用Unity的人:

 yield return new WaitForSeconds(startWait); while (numWaves < 4 && _myPauseState) { for (int i = 0; i < hazardCount;) { //spawn code } yield return new WaitForSeconds(waveWait); numWaves++; } 

实际上可能是一个ienumerator内部