我试图理解为什么下面的代码表现如下:
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中的工作方式极为相反(重用迭代不会重新启动它)。
答案 0 :(得分:2)
在C#中,当您在返回yield
或IEnumerable
的方法或属性中使用IEnumerable<T>
关键字时,方法(或属性)变为惰性序列,它不仅仅返回一个列表或类似数组的结构,它运行方法直到第一个yield
语句,然后暂停,直到下一个元素被&#34;拉出&#34;来自调用代码(您的Main
方法)。
因此,当您编写x = Foo()
时,您将x
设置为等于每次循环时执行Foo
主体的惰性序列。每次迭代此状态机时,Foo
体内的任何副作用都将执行一次。
Skip
和Take
每个都将延迟序列作为输入,然后根据需要使用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
成为第三个懒惰序列,通过将x
与Skip
操作合成而创建。此处不会立即迭代任何内容,但是当您foreach
z
时,它将执行Foo
的完整正文,但会退出&#34;前3个元素。
参见Jon Skeet的书:http://csharpindepth.com/Articles/Chapter6/IteratorBlockImplementation.aspx
答案 1 :(得分:0)
因为它是'IEnumerable'。这意味着每次调用枚举器(您的方法)时都会调用它。 如果您不希望它被调用两次而不是实现它:
var x = Foo().ToArray();