应该't sum方法在LINQ中延迟

本文关键字:LINQ 延迟 方法 sum 应该 | 更新日期: 2023-09-27 18:02:51

我有以下代码:

List<int> no = new List<int>() { 1, 2, 3, 4, 5 };
var res2 = no.Sum(a => a * a);
Console.WriteLine(res2);
no.Add(100);
Console.WriteLine(res2);

我期望得到以下结果:

55
10055

但都是55

55
55

我在这里看到的是关于延迟求值的,但没有帮助。Sum是一种扩展方法,但结果不是我所提到的。为什么?

应该't sum方法在LINQ中延迟

只有返回IEnumerable<T>的函数才能在Linq中被延迟(因为它们可以被包装在一个允许延迟的对象中)。

Sum的结果是int,因此它不可能以任何有意义的方式延迟它:

var res2 = no.Sum(a => a * a);
// res2 is now an integer with a value of 55
Console.WriteLine(res2);
no.Add(100);
// how are you expecting an integer to change its value here?
Console.WriteLine(res2);

您可以延迟执行(不是真正的延迟,而是显式地调用它),通过将lambda赋值给Func<T>:

List<int> no = new List<int>() { 1, 2, 3, 4, 5 };
Func<int> res2 = () => no.Sum(a => a * a);
Console.WriteLine(res2());
no.Add(100);
Console.WriteLine(res2());

这应该正确地给出5510055

一般来说,你可以假设只要一个Linq函数返回一个IEnumerable或IQueryable对象,那么它的执行就可能被延迟。

当返回值是TSource类型的一项或实现了iccollection 可以确保执行不会延迟(有谁知道任何异常吗?)

绝对确定:Enumerable函数的MSDN描述描述了该函数是否通过延迟执行实现。

例如Enumerable。选择:

这个方法是通过延迟执行来实现的。最直接的返回值是一个对象,它存储所有的信息执行操作所必需的。此方法表示的查询在对象被枚举之前不会执行…

Enumerable函数。Max不是通过延迟执行来实现的。因此,如果在计算Max后序列发生变化,则Max的结果不会改变。

参见Stackoverflow: Linq函数何时延迟?

一些LINQ方法(如WhereSelect)被延迟,因为计算一个结果与计算下一个结果是独立的。但是并不是所有操作IEnumerable<T>的方法都必须被延迟。

例如,Sum将把序列的所有元素约简为一个。因此,它可以不计算任何东西,也可以计算所有东西,但没有办法在两者之间做任何事情。它的作者选择打破他们通常的LINQ习惯,他们让它快速计算而不是懒惰计算。

这可以通过以下事实证明:IEnumerable<int>上的Sum有一个返回类型int,这是一个已经计算过的整数:

int res2 = no.Sum(a => a * a);

如果你想延迟Sum的计算,有一个简单的方法——使用Func<int>:

Func<int> res2 = () => no.Sum(a => a * a);

或者,您可以使它成为一个类似linq的扩展方法:

public static Func<int> LazySum(this IEnumerable<int> sequence, Func<int, int> selector)
        => () => sequence.Sum(selector);

然后像这样使用:

var res2 = no.LazySum(a => a * a);

无论您选择哪一个,您都可以验证它将为您提供延迟计算:

Console.WriteLine(res2()); // prints 55
no.Add(100);
Console.WriteLine(res2()); // prints 10055

返回IEnumerable的函数可以在Linq中被延迟,因为它们可以被包装在一个允许延迟的对象中。