调用包含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()
)。
有两件事在这里产生这种看似奇怪的行为:
-
只要达到
yield return
线,迭代器就会停止。 当您尝试从中获取另一个元素时,迭代器执行将从它离开的位置继续执行。 如果不这样做,它将永远不会恢复执行。 -
您实际上是两次执行迭代器。 你做的事情通常被称为“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 成品...
这说明了以上两点:
-
一旦返回值,就会停止
GetTimes
执行,并且只要请求另一个值,它就会从相同的状态恢复。 -
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)
方法吗? 你为什么不用它?