调用包含yield的方法后,不会执行代码行

考虑以下方法:

IEnumerable GetTimes(int count) { for (int i = 0; i < count; i++) yield return DateTime.Now; yield break; } 

现在,我想称之为:

  var times = GetTimes(2); Console.WriteLine("First element:" + times.Take(1).Single().ToString()); Console.WriteLine("Second element:" + times.Skip(1).Take(1).Single().ToString()); Console.WriteLine("Third element:" + times.Skip(2).Take(1).Single().ToString()); Console.WriteLine("Finished..."); 

但最后一行代码永远不会运行。 为什么?

线yield break; 因为交互者的工作方式而永远不会运行。

当你这样做时,不会执行迭代器方法GetTimes(int count)

 var times = GetTimes(2); 

相反,只要你从中提取一个值就会执行它(例如,当你做times.Take(1).Single().ToString()times.Take(1).Single().ToString() )。

有两件事在这里产生这种看似奇怪的行为:

  1. 只要达到yield return线,迭代器就会停止。 当您尝试从中获取另一个元素时,迭代器执行将从它离开的位置继续执行。 如果不这样做,它将永远不会恢复执行。

  2. 您实际上是两次执行迭代器。 你做的事情通常被称为“IEnumerable的多重枚举”

为了说明幕后实际发生的事情,让我们对你的GetTimes方法做一个小改动:让我们每次都不返回相同的日期,但每次调用它时我们都会返回前一个日期+ 1天。 我们还添加一些Console.WriteLine来跟踪执行情况。 所以,新的方法体可能如下所示:

 IEnumerable GetTimes(int count) { for (int i = 0; i < count; i++) { Console.WriteLine("returning the value with index " + i); yield return DateTime.Now.AddDays(i); } Console.WriteLine("About to hit the `yield break`! Awesome!"); yield break; } 

现在运行代码会产生以下输出:

返回索引为0的值
第一个要素:11/16/2012 11:34:46 PM
返回索引为0的值
返回索引为1的值
第二个要素:11/17/2012 11:34:46 PM
成品...

这说明了以上两点:

  1. 一旦返回值,就会停止GetTimes执行,并且只要请求另一个值,它就会从相同的状态恢复。

  2. Iterator执行两次(第二次使用Skip请求一个值, Take请求下一个值并使用它)。

好的,但为什么yield break;没有yield break; 被执行? 这是因为你的迭代器可以产生2个值,它只被调用2次,使得它在第二次yield return被击中后冻结。 如果你要从迭代器请求第三个元素,那么你的break行就会被击中。

现在,为了说明最后一行被击中的场景,让我们以通常的方式使用枚举器(使用foreach循环)。 将Console.WriteLine行替换为:

 foreach (var dateTime in times) Console.WriteLine(dateTime); 

此代码将生成以下输出:

返回索引为0的值
 11/17/2012 12:05:20 AM
返回索引为1的值
 11/18/2012 12:05:20 AM
即将打出'收益突破'! 真棒!

正如您所看到的, foreach消耗迭代器一直到结束,并且yield break线被击中。 您也可以通过在其上设置断点来确认。

假设你的意思是枚举器中的最后一行永远不会运行…该行

 yield break; 

不执行,因为您只从具有两个元素的序列中获取两个元素(使用代码的初始版本)。 枚举器后面的状态机永远不会足以执行该行。

yield break; 当你尝试从序列中取出3个元素时确实会运行。

我没有理由(没有可能抛出Exception)为什么不应该调用调用代码中的最后一行。

 Console.WriteLine("Finished..."); 

如果这就是你的意思,是抛出exception吗? 如果是这样,那么例外的本质是什么?

以前不正确的更新的重新审核

最初编写的代码确实执行该行

 Console.WriteLine("Finished..."); 

它没有执行

 yield break; 

出于前述原因。

随后添加到问题中的行

 Console.WriteLine("Third element:" + times.Skip(2).Take(1).Single().ToString()); 

没有成功,确实抛出一个InvalidOperationException(“Sequence包含无元素”),并且确实运行yield break; line,因为您尝试从只有2的序列中获取3个元素。

更新2

如果您正在尝试了解迭代器块和Yield关键字的工作方式,我强烈建议由Eric Lippert (Visual C#团队的首席开发人员)开始撰写一系列博客文章

http://blogs.msdn.com/b/ericlippert/archive/2009/07/09/iterator-blocks-part-one.aspx

当您尝试使用“第三个元素”(序列中的两个元素)时,将抛出exception,因为.Single()调用没有任何内容可以返回。

顺便问一下,你知道.ElementAt(int)方法吗? 你为什么不用它?