c# - LINQ 是否缓存计算值?

标签 c# .net linq clr4.0

假设我有以下代码:

var X = XElement.Parse (@"
    <ROOT>
        <MUL v='2' />
        <MUL v='3' />
    </ROOT>
");
Enumerable.Range (1, 100)
    .Select (s => X.Elements ()
        .Select (t => Int32.Parse (t.Attribute ("v").Value))
        .Aggregate (s, (t, u) => t * u)
    )
    .ToList ()
    .ForEach (s => Console.WriteLine (s));

.NET 运行时实际上在这里做什么?它是每次解析属性并将其转换为整数 100 次,还是它足够聪明地确定它应该缓存解析的值而不是对范围内的每个元素重复计算?

此外,我该如何自己解决这样的问题?

预先感谢您的帮助。

最佳答案

LINQ 和 IEnumerable<T>基于拉动的。这意味着在提取值之前,通常不会执行作为 LINQ 语句一部分的谓词和操作。此外,每次提取值时都会执行谓词和操作(例如,没有进行 secret 缓存)。

IEnumerable<T> 中提取数据由 foreach 完成这实际上是通过调用 IEnumerable<T>.GetEnumerator() 获取枚举器的语法糖并反复调用IEnumerator<T>.MoveNext()提取值。

LINQ 运算符,如 ToList() , ToArray() , ToDictionary()ToLookup()包装 foreach声明,因此这些方法将发挥作用。对于 Aggregate() 这样的运算符也可以这样说, Count()First() .这些方法的共同点是它们产生必须通过执行 foreach 来创建的单一结果。声明。

许多 LINQ 运算符生成一个新的 IEnumerable<T>顺序。当从结果序列中提取一个元素时,运算符从源序列中提取一个或多个元素。 Select()运算符是最明显的例子,但其他例子是 SelectMany() , Where() , Concat() , Union() , Distinct() , Skip()Take() .这些运算符不做任何缓存。然后从 Select() 中提取第 N 个元素它从源序列中提取第 N 个元素,使用提供的操作应用投影并返回它。这里没有什么 secret 。

其他 LINQ 运算符也产生新的 IEnumerable<T>序列,但它们是通过实际提取整个源序列、完成它们的工作然后生成新序列来实现的。这些方法包括 Reverse() , OrderBy()GroupBy() .但是,运算符(operator)完成的拉动仅在运算符(operator)本身被拉动时执行,这意味着您仍然需要 foreach在执行任何内容之前,在 LINQ 语句的“末尾”循环。您可能会争辩说这些运算符使用缓存是因为它们会立即提取整个源序列。但是,每次迭代运算符时都会构建此缓存,因此它实际上是一个实现细节,而不是神奇地检测到您正在应用相同的 OrderBy() 的东西。对同一个序列多次操作。


在您的示例中,ToList()会做一个拉。外层的 Action Select将执行 100 次。每次执行此操作时 Aggregate()将执行另一个解析 XML 属性的拉取操作。您的代码总共将调用 Int32.Parse() 200 次。

您可以通过拉取属性一次而不是每次迭代来改进这一点:

var X = XElement.Parse (@"
    <ROOT>
        <MUL v='2' />
        <MUL v='3' />
    </ROOT>
")
.Elements ()
.Select (t => Int32.Parse (t.Attribute ("v").Value))
.ToList ();
Enumerable.Range (1, 100) 
    .Select (s => x.Aggregate (s, (t, u) => t * u)) 
    .ToList () 
    .ForEach (s => Console.WriteLine (s)); 

现在Int32.Parse()只被调用 2 次。然而,代价是必须分配、存储和最终收集垃圾的属性值列表。 (当列表包含两个元素时,这不是一个大问题。)

请注意,如果您忘记了第一个 ToList()提取代码仍将运行但具有与原始代码完全相同的性能特征的属性。没有空间用于存储属性,但在每次迭代时解析它们。

关于c# - LINQ 是否缓存计算值?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10308381/

相关文章:

C# Treeview 键全路径

c# - 我如何加密任何用户在使用应用程序时都可以解密的存储密码?

c# - 自定义任务计划程序按日期时间启动任务

c# - 从方法返回内存流

c# - .NET 4.0 中网络共享的异常保存配置

c# - 使用 IExcelDataReader 从 excel 中获取正确的文本显示

.net - 使用托管组件链接到静态库

c# - PagedList与ViewModels和.Skip()。Take()一起使用

c# - Linq:日期小于 x,但下一个记录日期大于 x

c# - 查找包含在两个列表中的项目作为子字符串