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

Tej*_*ejs 6 c# linq ienumerable reflector lazy-evaluation

所以,这个问题刚刚被问到:

如何处理"无限"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++;
}
Run Code Online (Sandbox Code Playgroud)

有人可以解释为什么这是懒惰的评估?我在Reflector中查找了这段代码,我比开始时更困惑.

反射器输出:

public static IEnumerable<int> Numbers()
{
    return new <Numbers>d__0(-2);
}
Run Code Online (Sandbox Code Playgroud)

对于numbers方法,看起来为该表达式生成了一个新类型:

[DebuggerHidden]
public <Numbers>d__0(int <>1__state)
{
    this.<>1__state = <>1__state;
    this.<>l__initialThreadId = Thread.CurrentThread.ManagedThreadId;
}
Run Code Online (Sandbox Code Playgroud)

这对我来说毫无意义.我会认为这是一个无限循环,直到我把这些代码放在一起并自己执行.

编辑:所以我现在明白了.取()可以告诉大家,枚举了"结束"在foreach,当它真的没有,但不应号()被调用,在它的全部链接着在起飞前() ?Take结果是实际被枚举的内容,对吗?但是,如果没有对Numbers进行全面评估,那么如何执行?

EDIT2:这只是'yield'关键字强制执行的特定编译器技巧吗?

Jos*_*rke 1

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

foreach (var item in Numbers())
{
}
Run Code Online (Sandbox Code Playgroud)

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

代码惰性的原因也是代码有效的原因。本质上,Take 返回第一个项目,然后应用程序使用它,然后再获取另一个项目,直到获取 10 个项目。

编辑

这其实和take的加法没有关系。这些称为迭代器。C# 编译器对您的代码执行复杂的转换,从您的方法中创建一个枚举器。我建议阅读它,但基本上(这可能不是 100% 准确),您的代码将进入 Numbers 方法,您可以将其想象为初始化状态机。

一旦你的代码达到了yield返回,你本质上是在说Numbers()停止执行,给他们返回这个结果,然后当他们请求下一个项目时,在yield返回后的下一行恢复执行。

Erik Lippert 有一个关于迭代器其他方面的精彩系列