在多次枚举期间调用两次的Generator方法

时间:2017-06-30 13:11:22

标签: c# ienumerable

我试图理解为什么下面的代码表现如下:

using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApplication
{
    internal class Program
    {
        private static IEnumerable<int> Foo()
        {
            Console.WriteLine("Hello world!");
            for (var i = 0; i < 10; i++)
                yield return i;
        }

        public static void Main(string[] args)
        {
            var x = Foo();

            var y = x.Take(3);
            foreach (var i in y)
                Console.WriteLine(i);

            var z = x.Skip(3);
            foreach (var i in z)
                Console.WriteLine(i);
        }
    }
}

主要是我得到一个名为x的新“生成器”(借用python术语),然后我尝试枚举它两次。 我第一次使用前3个元素并打印出来:我希望程序打印出“Hello world!”接着是从0到2的数字。 第二次我采用相同的可枚举并跳过3个元素。 我希望程序打印6到9的数字,而不打印“Hello world!”第一

相反,程序第二次打印“Hello world!”第二次,然后从3开始枚举,最多9次。

我不明白:为什么Foo()会被调用两次?

EDIT1 :不重复。在编写可枚举时,链接的“重复”询问最佳实践,这里我很难使用它的值。

EDIT2 :我接受了一个老实说不是很好的答案,但是包含了一个引用链接,让我理解了这个问题。 让我感到困惑的是,当循环遍历IEnumerable时,foreach循环实际上是如何工作的,所以我将在这里解释它以供将来参考。

IEnumerable是值的序列 - 底层实现是无关紧要的,只要它能够按顺序为您提供值。 当你在它上面使用foreach循环时,它会创建一个Enumerator对象,该对象基本上是一个跟踪循环到达位置的游标。

所以发生的事情是第一个循环创建了第一个枚举器,而第一个枚举器必须启动我的生成器方法并打印第一个字符串。

第二个循环无法获取前一个枚举器(正如我所认为的那样)。 相反,它实例化第二个枚举器,然后重新启动生成器,从而打印第二个字符串。

对于任何阅读此内容的pythonista,请注意它与生成器在python中的工作方式极为相反(重用迭代不会重新启动它)。

2 个答案:

答案 0 :(得分:2)

在C#中,当您在返回yieldIEnumerable的方法或属性中使用IEnumerable<T>关键字时,方法(或属性)变为惰性序列,它不仅仅返回一个列表或类似数组的结构,它运行方法直到第一个yield语句,然后暂停,直到下一个元素被&#34;拉出&#34;来自调用代码(您的Main方法)。

因此,当您编写x = Foo()时,您将x设置为等于每次循环时执行Foo主体的惰性序列。每次迭代此状态机时,Foo体内的任何副作用都将执行一次。

SkipTake每个都将延迟序列作为输入,然后根据需要使用yield return元素,从而产生新的延迟序列。因此,当您编写y = x.Take(3)时,不会在那里进行任何迭代,但是当您foreach y时,它将有效地执行Foo,直到第三个yield语句。

Skip源代码: https://github.com/dotnet/corefx/blob/master/src/System.Linq/src/System/Linq/Skip.cs

同样z = x.Skip(3)使z成为第三个懒惰序列,通过将xSkip操作合成而创建。此处不会立即迭代任何内容,但是当您foreach z时,它将执行Foo的完整正文,但会退出&#34;前3个元素。

参见Jon Skeet的书:http://csharpindepth.com/Articles/Chapter6/IteratorBlockImplementation.aspx

答案 1 :(得分:0)

因为它是'IEnumerable'。这意味着每次调用枚举器(您的方法)时都会调用它。 如果您不希望它被调用两次而不是实现它:

var x = Foo().ToArray();