延期执行和急切评估

bab*_*u M 11 c# linq c#-3.0

能否请您在C#中通过热切评估给出一个延迟执行的示例?

我从MSDN上读到LINQ中的延迟执行可以通过惰性或急切评估来实现.我可以在互联网上找到延迟执行延迟执行的示例,但是我找不到任何带有急切评估的延迟执行的示例.

此外,延迟执行与惰性评估有何不同?在我看来,两者看起来都一样.你能为此提供任何一个例子吗?

Jul*_*lia 45

贝娄是我的回答,但也注意到Jon Skeet今天在他的博客上谈到了这个事实,即他对MSDN的"懒惰"含义并不完全正确,因为MSDN并不是很清楚他们使用时懒惰究竟是什么意思它在你有多懒惰?他的帖子有趣的阅读.

另外,维基百科假设应该为延迟评估维护三个规则,并且在MSDN中不尊重第三个点,这意味着如果GetEnumerator再次调用表达式将被多次评估(通过规范重置不会在使用yield关键字生成的枚举器对象上实现而且大多数linq目前使用它)


考虑一个功能

int Computation(int index)
Run Code Online (Sandbox Code Playgroud)

立即执行

IEnumerable<int> GetComputation(int maxIndex)
{
    var result = new int[maxIndex];
    for(int i = 0; i < maxIndex; i++)
    {
        result[i] = Computation(i);
    }
    return result;
}
Run Code Online (Sandbox Code Playgroud)
  • 当函数被调用时Computation执行maxIndex次数
  • GetEnumerator 返回枚举器的新实例,不再执行任何操作.
  • 每次调用都MoveNext将存储在下一个Array单元格中的值放入其中的Current成员中IEnumerator.

成本:大前期,枚举期间小(仅一份)

延迟但急切的执行

IEnumerable<int> GetComputation(int maxIndex)
{
    var result = new int[maxIndex];
    for(int i = 0; i < maxIndex; i++)
    {
        result[i] = Computation(i);
    }
    foreach(var value in result)
    {
        yield return value;
    }
}
Run Code Online (Sandbox Code Playgroud)
  • 当函数被调用时,自动生成的类的实例(在规范中称为"可枚举对象")实现IEnumerable被创建,并且参数(maxIndex)的副本存储在其中.
  • GetEnumerator 返回枚举器的新实例,不再执行任何操作.
  • MoveNext执行maxIndex 的第一次调用乘以compute方法,将结果存储在数组中并Current返回第一个值.
  • 每次后续调用MoveNext都会将Current一个值存入数组中.

成本:没有任何前期,枚举开始时大,枚举时小(只有一个副本)

延迟执行和延迟执行

IEnumerable<int> GetComputation(int maxIndex)
{
    for(int i = 0; i < maxIndex; i++)
    {
        yield return Computation(i);
    }
}
Run Code Online (Sandbox Code Playgroud)
  • 当函数被调用时,与惰性执行情况相同.
  • GetEnumerator 返回枚举器的新实例,不再执行任何操作.
  • 每次调用MoveNext执行一次Computation代码,将值放入Current并让调用者立即对结果执行操作.

大多数linq使用延迟和延迟执行,但有些函数不能像排序那样.

成本:没有任何前期,在枚举期间中等(计算在那里执行)

总结一下

  • 立即意味着在函数中完成计算/执行并在函数返回后完成.(完全热切的评估,因为大多数C#代码都有)
  • Deferred/Eager意味着大部分工作将在第一次MoveNextIEnumerator创建实例时完成(因为IEnumerable它是在何时GetEnumerator被调用)
  • 延迟/ 懒惰意味着每次MoveNext调用工作都会被调用,但之前没有.

并行LINQ的做法略有不同,因为从调用者的角度来看,计算可以被认为是延迟/延迟,但是在内部,一旦枚举开始,一些元素的计算就会并行开始.结果是,如果下一个值已经存在,则立即得到它,否则您将不得不等待它.

  • 差异的最佳示例是"选择"和"反向":两者都是延迟的,源可枚举只是通过调用方法存储的.Select是懒惰的,因为每次在结果上调用GetNext时,在源上调用GetNext,它甚至可以使用无限的源,如果源完成了复杂的计算,则会分配负载.另一方面反向是急切的,它需要在返回它的第一个元素之前遍历整个源可枚举,如果源进行复杂的计算,它们将在结果的第一个GetNext调用上执行. (4认同)

Joe*_*nez 2

您可以急切地评估延迟执行 IEnumerable 的一种方法是使用 linq 的 .ToArray() 函数将其简单地转换为数组。

var evaluated = enumerable.ToArray();
Run Code Online (Sandbox Code Playgroud)

这会强制对完整的可枚举进行评估,然后您就拥有了可以执行任何操作的数组。