代码之家  ›  专栏  ›  技术社区  ›  Tejs

有人能解释一下这个懒惰的评估代码吗?

  •  6
  • Tejs  · 技术社区  · 16 年前

    所以,刚才有人问这个问题,所以:

    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枚举已经“结束”,而实际上它还没有结束,但是在链接到Take()之前,不应该完整地调用Numbers()吗?Take的结果是实际枚举的结果,对吗?但当数字还没有完全计算出来时,Take是如何执行的呢?

    编辑2

    3 回复  |  直到 9 年前
        1
  •  1
  •   JoshBerke    16 年前

    这不是一个无限循环的原因是,根据Linq的Take(10)调用的用法,您只需要枚举10次。如果你写的代码是这样的:

    foreach (var item in Numbers())
    {
    }
    

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

    代码懒惰的原因也是代码工作的原因。实际上,Take返回第一个项目,然后应用程序使用,然后再使用另一个项目,直到它使用了10个项目。

    编辑

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

    一旦你的代码命中了一个yield return,本质上就是说Numbers()停止执行,把这个结果还给他们,然后当他们要求下一个项目时,在yield return之后的下一行继续执行。

    Erik Lippert has a great series

        2
  •  2
  •   Patrick Karcher    16 年前

    这与:

    • 枚举的性质及其应用 陈述

    全部的 它的项目,它只是给你下一个项目。它决定了这个项目是什么(例如,有些集合是有序的,有些则不是。有些人不保证有一个特定的顺序,但似乎总是给他们在同一顺序你把他们)。

    Take() 将枚举10次,得到前10项。你能做到的 Take(100000000) ,它会给你很多数字。但你只是在做 Take(10) . 它只是问 Numbers() 对于下一项。10次。

    这10项中的每一项, Numbers 给出下一项。要了解如何操作,您需要仔细阅读收益率报表。它是 syntactic sugar 为了更复杂的事情。 Yield 非常强大(我是一个VB开发人员,很恼火我仍然没有它。)它不是一个函数;这是一个有一定限制的关键词。它使得定义枚举数比其他方法简单得多。

    其他IEnumerable扩展方法总是遍历每个项。打电话给AsList会把事情搞砸的。使用它大多数LINQ查询都会破坏它。

        3
  •  0
  •   Rubys    16 年前


    foreach将在每次迭代中检查枚举器是否已到达终点,如果未到达终点,它将继续。你的实际枚举器永远不会结束,但这并不重要。这是懒散的评估。
    枚举器将生成结果“live”。
    这意味着如果你想写,那么在这里,循环只会执行三次。枚举器中仍有一些项“遗留”在其中,但由于此时没有方法需要它们,因此不会生成这些项。
    如果你试着像函数所暗示的那样生成从0到无穷大的所有数字,并且一次返回所有的数字,那么这个只使用10个数字的程序会慢得多。这就是懒惰评估的好处——从不使用的东西永远不会被计算出来。