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

时间:2012-11-16 21:12:43

标签: c# 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...");

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

3 个答案:

答案 0 :(得分:2)

由于interators的工作方式,行yield break;永远不会运行。

执行

时不执行迭代器方法GetTimes(int count)
var times = GetTimes(2);

相反,它会在您从中提取值时执行(例如,当您执行times.Take(1).Single().ToString()时)。

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

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

  2. 您实际上正在执行两次迭代器。你做的事情通常被称为"multiple enumeration of IEnumerable"

  3. 为了说明幕后实际发生的事情,让我们对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;
    }
    

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

    returning the value with index 0
    First element:11/16/2012 11:34:46 PM
    returning the value with index 0
    returning the value with index 1
    Second element:11/17/2012 11:34:46 PM
    Finished...
    

    这说明了以上两点:

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

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

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

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

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

      此代码将产生以下输出:

      returning the value with index 0
      11/17/2012 12:05:20 AM
      returning the value with index 1
      11/18/2012 12:05:20 AM
      About to hit the `yield break`! Awesome!
      

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

答案 1 :(得分:1)

假设你的意思是枚举器中的最后一行 从不运行......行

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

答案 2 :(得分:0)

当您尝试使用“第三个元素”(序列中的两个元素)时,将抛出异常,因为.Single()调用无法返回。

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