你能给我一个在 C# 中使用急切求值的延迟执行的例子吗?
我从 MSDN 了解到,LINQ 中的延迟执行可以通过惰性求值或急切求值来实现。我可以在互联网上找到使用惰性求值的延迟执行的示例,但是我找不到任何使用急切求值的延迟执行的示例。
此外,延迟执行与惰性求值有何不同?在我看来,两者看起来都一样。您能否也为此提供任何示例?
最佳答案
下面是我的回答,但也请注意 Jon Skeet 今天在他的博客上谈到了一个事实,即他对 MSDN 中“懒惰”的含义并不完全满意,因为 MSDN 并不清楚懒惰的确切含义当他们在 Just how lazy are you ? 中使用它时他的帖子值得一读。
另外Wikipedia假设应该为惰性求值维护三个规则,并且在 MSDN 中不遵守第三点,这意味着如果再次调用 GetEnumerator
,表达式将被求值不止一次(根据规范 Reset is not implemented on使用 yield
关键字生成的枚举器对象,目前大多数 linq 都使用它)
考虑一个函数
int Computation(int index)
立即执行
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
都会将存储在下一个数组单元格中的值放入IEnumerator
的Current
成员中,仅此而已。
成本:前期大,枚举期间小(仅副本)
延迟但急切的执行
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
执行计算方法的 maxIndex 次,将结果存储在数组中,Current
将返回第一个值。 - 对
MoveNext
的每次后续调用都会将存储在数组中的值放入Current
。
成本:前期无成本,枚举开始时大,枚举期间小(仅副本)
延迟和惰性执行
IEnumerable<int> GetComputation(int maxIndex)
{
for(int i = 0; i < maxIndex; i++)
{
yield return Computation(i);
}
}
- 当调用函数时,会发生惰性执行情况。
GetEnumerator
返回一个新的枚举器实例。- 每次调用
MoveNext
都会执行一次Computation
代码,将值放入Current
并让调用者立即对结果进行操作。
大多数linq使用延迟和惰性执行,但有些函数不能像排序那样。
成本:前期无成本,枚举期间适中(计算在那里执行)
总结
- 立即意味着计算/执行在函数中完成,并在函数返回后完成。 (与大多数 C# 代码一样,完全 eager 评估)
- 延期/Eager意味着大部分工作将在第一个
MoveNext
或创建IEnumerator
实例时完成(对于IEnumerable
是调用 GetEnumerator
) - 延期/Lazy意味着每次调用
MoveNext
时都会完成工作,但之前不会。
Parallel LINQ它的做法略有不同,因为从调用者的角度来看,计算可以被认为是延迟的/惰性的,但在内部,一旦枚举开始,一些元素的计算就会并行开始。结果是,如果下一个值已经存在,您会立即获得它,否则您将不得不等待它。
关于c# - 延迟执行和急切评估,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2515796/