有人可以解释这个懒惰的评估代码吗?
所以,这个问题只是问的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()?采取的结果是什么实际上正在枚举,正确的?但是,如果Numbers没有完全评估,那么执行如何执行?
EDIT2:那么这只是'yield'关键字执行的特定编译器技巧吗?
这不是一个无限循环的原因是根据使用Linq的Take(10)调用,你只能枚举10次。现在,如果你写的代码是这样的:
foreach (var item in Numbers())
{
}
现在这是一个无限循环,因为您的枚举器将始终返回一个新值。 C#编译器获取这些代码并将其转换为状态机。如果您的枚举器没有警卫子句来破坏执行,那么调用者必须在您的示例中执行它。
代码懒惰的原因也是代码工作的原因。从本质上讲,返回第一个项目,然后你的应用程序消耗,然后它需要另一个,直到它有10个项目。
编辑
这实际上无关与加拿的。这些被称为迭代器。 C#编译器对您的代码执行复杂的转换,从您的方法中创建一个枚举器。我建议阅读它,但基本上(这可能不是100%准确),您的代码将输入Numbers方法,您可以设想为初始化状态机。
一旦你的代码达到收益率回报,你基本上说Numbers()停止执行了,让他们回来这个结果,然后当他们要求下一个项目在收益率返回后的下一行继续执行。
的其它方面,这必须与:
- 的IEnumerable做什么时,某些方法被称为
- 枚举的性质和产量声明
当您枚举任何类型的IEnumerable时,该类将为您提供下一个它会给你的东西。它不会对全部做任何事情,它只是给你下一个项目。它决定该项目将会是什么。 (例如,某些集合是有序的,有些不是集合的,有些集合不保证特定的顺序,但似乎总是按照您放入的顺序将其返回)。
了IEnumerable扩展方法Take()
将枚举10次,得到的前10个项目。你可以做Take(100000000)
,它会给你很多数字。但你只是在做Take(10)
。它只是要求Numbers()
下一个项目。 。 。 10倍。
每个那些10项,Numbers
给出了一个项目。要理解如何,您需要阅读Yield语句。更复杂的是syntactic sugar。 Yield非常强大。 (我是一个VB开发人员,非常恼火,我仍然没有它。)这不是一个函数;这是一个有一定限制的关键字。而且它使得定义一个枚举器比其他方法更容易。
其他IEnumerable扩展方法总是遍历每一个项目。调用.AsList会炸掉它。使用它大部分的LINQ查询会炸掉它。
使用yield来编写自定义枚举器非常有趣。 – JoshBerke 2010-04-29 19:24:26
基本上,你的Numbers()函数创建一个枚举器。
foreach将在每次迭代中检查enumrator是否已达到结尾,如果没有,它将继续。你真正的恩将永远不会结束,但那并不重要。这是懒惰评估。
枚举器将生成结果“生活”。
这意味着如果你想写(3)在那里,循环只执行三次。枚举器仍然会有一些“剩余”的项目,但是它们不会生成,因为目前没有方法需要它们。
如果你想尝试从0到无穷大的所有数字,就像函数所暗示的那样,并且一次返回所有的数字,那么这个只使用10个数字的程序会慢很多。这是懒惰评估的好处 - 从未使用过的东西永远不会计算。
是的,但是为了继续进行接听呼叫,Numbers()本身不必进行全面评估吗?我知道数字本身是一个无限循环。为什么添加.Take()会突然停止Numbers的整体评估? – Tejs 2010-04-29 19:24:25
有趣。感谢您的链接!这个问题(和代码)让我做了双重考虑,现在我意识到我还有更多要学习Enumerables! – Tejs 2010-04-29 19:37:40