考虑以下方法:
IEnumerable<DateTime> 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...");
但是最后一行代码永远不会运行。为什么?
由于interator的工作方式,行yield break;
从不运行。
执行时不执行迭代器方法GetTimes(int count)
var times = GetTimes(2);
相反,每当您从中提取值时(例如,当您执行times.Take(1).Single().ToString()
时),都会执行它。
有两件事产生了这种看似奇怪的行为:
每当
yield return
线被击中时,迭代程序就会停止。当你试图从迭代器中获取另一个元素时,迭代器的执行会从它离开的地方恢复。如果你不这样做,它将永远不会恢复执行。实际上,您正在执行迭代器两次。你做的事情通常被称为"IEnumerable的多重枚举">
为了说明幕后实际发生了什么,让我们对GetTimes
方法进行一个小的更改:我们不是每次都返回相同的日期,但每次调用它时,我们都会返回前一个日期+1天。让我们还添加一些Console.WriteLine
来跟踪执行。因此,新的方法体可能看起来像这样:
IEnumerable<DateTime> 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的值第一要素:2012年11月16日下午11:34:46返回索引为0的值返回索引为1的值第二要素:2012年11月17日下午11:34:46已完成
这说明了以上两点:
GetTimes
执行在返回值后立即停止,并且在请求另一个值后立即从相同状态恢复。迭代程序执行两次(第二次使用
times
时,Skip
请求一个值,Take
请求下一个值并使用它)。
可以,但为什么yield break;
没有执行?这是因为迭代器可以产生2个值,并且只调用2次,使其在第二个yield return
被命中后冻结。如果您要从迭代器请求第三个元素,那么break
行就会被命中
现在,为了说明最后一行被命中的场景,让我们以通常的方式使用枚举器(使用foreach
循环)。将Console.WriteLine
行替换为:
foreach (var dateTime in times)
Console.WriteLine(dateTime);
此代码将产生以下输出:
返回索引为0的值2012年11月17日上午12:05:20返回索引为1的值2012年11月18日凌晨12:05:20即将达到"屈服点"!太棒了
正如您所看到的,foreach
一直消耗迭代器到最后,yield break
行被命中。您也可以通过设置断点来确认这一点。
假设您的意思是枚举器中的最后一行从未运行。。。线路
yield break;
不执行,因为您只从具有两个元素的序列中获取两个元素(带有代码的初始版本)。枚举器后面的状态机永远无法执行该行。
当您尝试从序列中获取3个元素时,yield break;
确实会运行。
我看不出为什么不应该调用调用代码中的最后一行。
Console.WriteLine("Finished...");
如果这就是你的意思,那么抛出异常了吗?如果是,例外情况的性质是什么?
重写以前的错误更新
最初编写的代码执行行
Console.WriteLine("Finished...");
它不执行
yield break;
由于前面所述的原因。
随后添加到问题中的行
Console.WriteLine("Third element:" + times.Skip(2).Take(1).Single().ToString());
没有成功,并且确实抛出了InvalidOperationException("Sequence contains no elements"),并且确实运行了yield break;
行,因为您试图从只有2个元素的序列中获取3个元素。
更新2
如果你想了解迭代器块和Yield关键字是如何工作的,我强烈建议Eric Lippert(Visual C#团队的主要开发人员)从开始写一系列博客文章
http://blogs.msdn.com/b/ericlippert/archive/2009/07/09/iterator-blocks-part-one.aspx
当您尝试获取"Third element"(序列中的两个元素中的一个)时,将抛出异常,因为.Single()
调用没有任何可返回的内容。
顺便问一下,你知道.ElementAt(int)
方法吗?你为什么不用那个?