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



考虑以下方法:

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()时),都会执行它。

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

  1. 每当yield return线被击中时,迭代程序就会停止。当你试图从迭代器中获取另一个元素时,迭代器的执行会从它离开的地方恢复。如果你不这样做,它将永远不会恢复执行。

  2. 实际上,您正在执行迭代器两次。你做的事情通常被称为"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已完成

这说明了以上两点:

  1. GetTimes执行在返回值后立即停止,并且在请求另一个值后立即从相同状态恢复。

  2. 迭代程序执行两次(第二次使用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)方法吗?你为什么不用那个?

最新更新