所以,这个问题只是在SO上问:
How to handle an "infinite" IEnumerable?
我的示例代码:
public static void Main(string[] args)
{
foreach (var item in Numbers().Take(10))
Console.WriteLine(item);
Console.ReadKey();
}
public static IEnumerable<int> Numbers()
{
int x = 0;
while (true)
yield return x++;
}
有人可以解释为什么这是懒惰的评估?我在Reflector中查找了这段代码,我比开始时更困惑。
反射器输出:
public static IEnumerable<int> Numbers()
{
return new <Numbers>d__0(-2);
}
对于数字方法,并且看起来为该表达式生成了一个新类型:
[DebuggerHidden]
public <Numbers>d__0(int <>1__state)
{
this.<>1__state = <>1__state;
this.<>l__initialThreadId = Thread.CurrentThread.ManagedThreadId;
}
这对我没有意义。我会假设它是一个无限循环,直到我把这些代码放在一起并自己执行它。
编辑:所以我现在明白了.Take()可以告诉foreach枚举已经'结束',当它真的没有,但不应该调用Numbers()在链接前往Take()之前是完整的吗? Take结果是实际被枚举的内容,对吗?但是,如果没有完全评估Numbers,请如何执行?
EDIT2 :这只是'yield'关键字强制执行的特定编译器技巧吗?
答案 0 :(得分:2)
这必须与:
当您枚举任何类型的IEnumerable时,该类会为您提供它将为您提供的下一个项目。它不会对所有项目做些什么,它只会给你下一个项目。它决定该项目将是什么。 (例如,有些集合是有序的,有些则不是。有些不保证特定的订单,但似乎总是以你放入它们的顺序给它们。)。
IEnumerable扩展方法Take()
将枚举10次,获得前10个项目。你可以做Take(100000000)
,它会给你很多数字。但你只是做Take(10)
。它只是询问Numbers()
下一个项目。 。 。 10次。
这10个项目中的每一个Numbers
都会给出下一个项目。要了解如何操作,您需要阅读Yield语句。对于更复杂的事情,它是syntactic sugar。 Yield非常强大。 (我是一名VB开发人员,非常恼火,我仍然没有。)这不是一个功能;这是一个有某些限制的关键字。它使定义一个枚举器比原本更容易。
其他IEnumerable扩展方法总是遍历每个项目。调用.AsList会搞砸它。使用它,大多数LINQ查询都会破坏它。
答案 1 :(得分:1)
这不是无限循环的原因是你只根据Linq的Take(10)调用使用10次枚举。现在,如果您编写的代码如下:
foreach (var item in Numbers())
{
}
现在这是一个无限循环,因为您的枚举器将始终返回一个新值。 C#编译器接受此代码并将其转换为状态机。如果您的枚举器没有保护条款来中断执行,那么调用者必须在您的样本中执行哪一项。
代码惰性的原因也是代码工作的原因。基本上Take返回第一个项目,然后你的应用程序消耗,然后它需要另一个,直到它已经采取了10个项目。
修改强>
这实际上与添加take无关。这些被称为迭代器。 C#编译器对您的代码执行复杂的转换,从您的方法中创建枚举器。我建议阅读但基本上(这可能不是100%准确),您的代码将进入Numbers方法,您可以设想启动状态机。
一旦你的代码达到收益率回报,你本质上就是说Numbers()停止执行给他们这个结果,然后当他们要求下一个项目在收益率返回后的下一行继续执行。
关于迭代器的misc方面的答案 2 :(得分:0)
基本上,您的Numbers()函数会创建一个枚举器
foreach将在每次迭代中检查枚举器是否已到达结束,如果没有,则将继续。你的特定囚犯将永远不会结束,但这并不重要。这是被懒惰评估的
枚举器将生成“实时”结果
这意味着如果你要写。在那里拿(3),循环只会被执行三次。该枚举器仍然会在其中“留下”一些项目,但由于目前没有方法需要它们,因此不会生成它们。
如果你试图像函数所暗示的那样生成从0到无穷大的所有数字,并一次性返回它们,那么只使用其中10个的程序会慢得多。这是懒惰评估的好处 - 从未使用过的是从未计算过的。