1

考虑以下方法:

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

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

4

3 回答 3

2

由于interators的工作方式,这条线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请求下一个值并使用它)。SkipTake

好的,但是为什么不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行被命中。您也可以通过在其上设置断点来确认这一点。

于 2012-11-16T21:42:02.870 回答
1

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

yield break;

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

yield break;您尝试从序列中获取 3 个元素时,确实会运行。

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

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

于 2012-11-16T21:14:27.723 回答
0

当您尝试获取“第三个元素”(序列中的两个元素)时,将引发异常,因为.Single()调用没有可返回的内容。

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

于 2012-11-16T21:31:39.137 回答