C# 在多次枚举期间调用了两次生成器方法
我试图理解为什么以下代码的行为如此:C# 在多次枚举期间调用了两次生成器方法,c#,ienumerable,C#,Ienumerable,我试图理解为什么以下代码的行为如此: using System; using System.Collections.Generic; using System.Linq; namespace ConsoleApplication { internal class Program { private static IEnumerable<int> Foo() { Console.WriteLine("Hello w
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);
}
}
}
使用系统;
使用System.Collections.Generic;
使用System.Linq;
命名空间控制台应用程序
{
内部课程计划
{
私有静态IEnumerable Foo()
{
控制台。WriteLine(“你好,世界!”);
对于(变量i=0;i<10;i++)
收益率i;
}
公共静态void Main(字符串[]args)
{
var x=Foo();
变量y=x.Take(3);
foreach(变量i在y中)
控制台写入线(i);
var z=x.Skip(3);
foreach(变量i在z中)
控制台写入线(i);
}
}
}
大体上,我得到了一个名为x的新“生成器”(请原谅python术语),然后我尝试枚举它两次。
第一次打印前3个元素时:我希望程序打印“Hello world!”,后面是从0到2的数字。
第二次使用相同的可枚举项并跳过3个以上的元素。
我希望程序打印6到9之间的数字,而不首先打印“Hello world!”
相反,程序第二次打印“Hello world!”第二次,然后从3开始枚举,直到9
我不明白:为什么调用Foo()两次
EDIT1:不是重复的。链接的“duplicate”询问编写枚举时的最佳实践,在这里我很难使用它的值
EDIT2:我接受了一个诚实地说不太好的答案,但其中包含一个链接,指向一个使我理解问题的参考。
让我困惑的是,当在IEnumerable上循环时,foreach循环实际上是如何工作的,所以我将在这里解释它以供将来参考
IEnumerable是值的序列——只要它是能够按顺序给你值的东西,那么底层的实现是不相关的。
当对其使用foreach
循环时,它会创建一个枚举器对象,它基本上是一个跟踪循环到达位置的光标
因此,第一个循环创建了第一个枚举数,该枚举数必须启动生成器方法并打印第一个字符串
第二个循环无法提取上一个枚举数(我相信它是这样做的)。
相反,它实例化第二个枚举器,该枚举器反过来重新启动生成器,从而打印第二个字符串
对于任何阅读本文的pythonista,请注意,这与python中生成器的工作方式相反(重用iterable不会重新启动它)。在C#中,当您在返回IEnumerable
或IEnumerable
方法(或属性)的方法或属性中使用yield
关键字时成为一个惰性序列,它不仅返回类似于列表或数组的结构,还运行该方法,直到第一个yield
语句,然后暂停,直到下一个元素从调用代码中“提取”(您的Main
方法)
因此,当您编写x=Foo()
时,您将x
设置为一个惰性序列,该序列每次循环执行Foo
的主体。Foo
主体中的任何副作用将在此状态机的每次迭代中执行一次
Skip
和Take
分别将一个惰性序列作为输入,然后根据需要返回
元素,生成一个新的惰性序列。因此,当您编写y=x.Take(3)
时,不会在那里迭代任何内容,但是当您编写foreach
y
时,它将有效地执行Foo
,直到第三条语句产生
为止
跳过
源代码:
同样地,z=x.Skip(3)
通过使用Skip
操作组合x
创建第三个惰性序列。此处不会立即迭代任何内容,但当您foreach
z
时,它将执行Foo
的全部内容,但会“抛出”前3个元素
请参阅Jon Skeet的书:,因为它是“IEnumerable”。这意味着每次调用枚举数(您的方法)时都会调用它。 如果您不希望它被调用两次,那么您应该实现它:
var x=Foo().ToArray()代码>可能的重复我不同意它是重复的。这个问题询问了在编写iterable时的最佳实践;我正在试图理解为什么我的生成器会被重置,尽管我并不期望它会被重置。x不会在后续调用之间保存状态,您总是会得到Foo()生成的完整枚举(包括副作用)。@HelmutD,谢谢,这就是原因。我来自python,在python中,保持其状态的是生成器本身。。。但是我的状态机没有被处理。我仍然有一个有效的参考资料。我没有做z=Foo().Skip(3)
,我正在做z=x.Skip(3)
,x已经被迭代过了。@JamesFaix解释是错误的,调用迭代的是foreach
,而不是Skip
或Take
你认为Skip
和Take
是如何实现的?使用延迟执行-删除foreaches,并且不会在allExactly处调用Foo-因此,“当您写入y=x.Take(3)时,Foo的主体被迭代最多3个元素,然后状态机被释放”是不正确的