有人可以解释这个懒惰的评估代码吗?

时间:2010-04-29 19:14:28

标签: c# linq ienumerable reflector lazy-evaluation

所以,这个问题只是在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'关键字强制执行的特定编译器技巧吗?

3 个答案:

答案 0 :(得分:2)

这必须与:

  • 调用某些方法时,iEnumerable的作用
  • 枚举的性质和收益声明

当您枚举任何类型的IEnumerable时,该类会为您提供它将为您提供的下一个项目。它不会对所有项目做些什么,它只会给你下一个项目。它决定该项目将是什么。 (例如,有些集合是有序的,有些则不是。有些不保证特定的订单,但似乎总是以你放入它们的顺序给它们。)。

IEnumerable扩展方法Take()将枚举10次,获得前10个项目。你可以做Take(100000000),它会给你很多数字。但你只是做Take(10)。它只是询问Numbers()下一个项目。 。 。 10次​​。

这10个项目中的每一个Numbers都会给出下一个项目。要了解如何操作,您需要阅读Yield语句。对于更复杂的事情,它是syntactic sugarYield非常强大。 (我是一名VB开发人员,非常恼火,我仍然没有。)这不是一个功能;这是一个有某些限制的关键字。它使定义一个枚举器比原本更容易。

其他IEnumerable扩展方法总是遍历每个项目。调用.AsList会搞砸它。使用它,大多数LINQ查询都会破坏它。

答案 1 :(得分:1)

这不是无限循环的原因是你只根据Linq的Take(10)调用使用10次枚举。现在,如果您编写的代码如下:

foreach (var item in Numbers())
{
}

现在这是一个无限循环,因为您的枚举器将始终返回一个新值。 C#编译器接受此代码并将其转换为状态机。如果您的枚举器没有保护条款来中断执行,那么调用者必须在您的样本中执行哪一项。

代码惰性的原因也是代码工作的原因。基本上Take返回第一个项目,然后你的应用程序消耗,然后它需要另一个,直到它已经采取了10个项目。

修改

这实际上与添加take无关。这些被称为迭代器。 C#编译器对您的代码执行复杂的转换,从您的方法中创建枚举器。我建议阅读但基本上(这可能不是100%准确),您的代码将进入Numbers方法,您可以设想启动状态机。

一旦你的代码达到收益率回报,你本质上就是说Numbers()停止执行给他们这个结果,然后当他们要求下一个项目在收益率返回后的下一行继续执行。

关于迭代器的misc方面的

Erik Lippert has a great series

答案 2 :(得分:0)

基本上,您的Numbers()函数会创建一个枚举器 foreach将在每次迭代中检查枚举器是否已到达结束,如果没有,则将继续。你的特定囚犯将永远不会结束,但这并不重要。这是被懒惰评估的 枚举器将生成​​“实时”结果 这意味着如果你要写。在那里拿(3),循环只会被执行三次。该枚举器仍然会在其中“留下”一些项目,但由于目前没有方法需要它们,因此不会生成它们。
如果你试图像函数所暗示的那样生成从0到无穷大的所有数字,并一次性返回它们,那么只使用其中10个的程序会慢得多。这是懒惰评估的好处 - 从未使用过的是从未计算过的。