关于c#:延迟执行和急切评估

Deferred execution and eager evaluation

你能给我一个例子来说明C中的延迟执行和热切的评估吗?

我从msdn中了解到,在linq中延迟的执行可以通过懒惰的或热切的评估来实现。我可以在Internet上找到延迟执行的例子,但我找不到任何延迟执行的例子,这些例子都是通过延迟执行进行的。

此外,延迟执行与延迟评估有什么不同?在我看来,两者看起来是一样的。你也能举个例子吗?


下面是我的答案,但也请注意,乔恩·斯基特今天在他的博客《关于一个事实》中谈到了这一点,即他对"懒惰"的msdn含义并不完全满意,因为msdn并不清楚"懒惰"到底是什么意思,当他们用它来表示你有多懒惰时?他的文章很有趣。

另外,wikipedia认为应该为懒惰的评估维护三个规则,并且在msdn意义上不尊重第三点,因为如果再次调用GetEnumerator,表达式将被多次评估(根据规范,重置不在使用yield关键字生成的枚举器对象上实现,并且大多数linq使用它cur)。右)

考虑一个函数

1
int Computation(int index)

立即执行

1
2
3
4
5
6
7
8
9
IEnumerable<int> GetComputation(int maxIndex)
{
    var result = new int[maxIndex];
    for(int i = 0; i < maxIndex; i++)
    {
        result[i] = Computation(i);
    }
    return result;
}
  • 当函数被称为Computation时,执行maxIndex次。
  • GetEnumerator返回枚举器的一个新实例,不再执行任何操作。
  • 每次调用MoveNext时,都会将存储在IEnumeratorCurrent成员的下一个数组单元中的值放入,仅此而已。

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

延期但急于执行

1
2
3
4
5
6
7
8
9
10
11
12
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;
    }
}
  • 当调用函数时,将创建实现IEnumerable的自动生成类(在规范中称为"可枚举对象")的实例,并在其中存储参数(maxIndex的副本。
  • GetEnumerator返回枚举器的一个新实例,不再执行任何操作。
  • MoveNext的第一个调用执行的最大值乘以计算方法,将结果存储在数组中,Current将返回第一个值。
  • MoveNext的每个后续调用都会将存储在数组中的值放入Current

成本:前期无,枚举开始时大,枚举过程中小(只有一个副本)

拖延和懒惰的执行

1
2
3
4
5
6
7
IEnumerable<int> GetComputation(int maxIndex)
{
    for(int i = 0; i < maxIndex; i++)
    {
        yield return Computation(i);
    }
}
  • 当调用函数时,会发生与延迟执行相同的情况。
  • GetEnumerator返回枚举器的一个新实例,不再执行任何操作。
  • 每次调用MoveNext执行一次Computation代码,将值放入Current中,让调用者立即对结果采取行动。

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

成本:不预先计算,在枚举期间中等(计算在那里执行)

总结

  • immediate是指计算/执行在函数中完成,并在函数返回后完成。(与大多数C代码一样,完全渴望评估)
  • 延迟/热切意味着大部分工作将在第一个MoveNext或创建IEnumerator实例时(对于IEnumerable,调用GetEnumerator)完成。
  • 延迟/延迟意味着每次调用MoveNext时都将完成工作,但之前什么都没有。

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


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

1
var evaluated = enumerable.ToArray();

这将强制计算完全可枚举的值,然后您就可以使用数组来执行任何操作。