LINQ 表达式中的多次枚举

发布: (2025年12月15日 GMT+8 14:00)
4 min read
原文: Dev.to

Source: Dev.to

为什么多次枚举是个问题

  • 重复工作 – 每次枚举都会创建一个新的枚举器,从序列的开头重新开始。
  • 资源消耗 – 对于查询数据库、调用 Web 服务或读取文件的情况,每次枚举都会重复执行代价高的操作。
  • 意外的副作用 – 某些枚举器可能具有有状态的副作用(例如日志记录、计数器),这些副作用会被多次触发。

及早物化

为了避免重复枚举,在知道需要遍历多次时就将序列物化。物化会把惰性 IEnumerable 转换为具体的集合(ListT[] 等),从而可以重复使用而无需重新执行底层查询。

// Lazy sequence
IEnumerable numbers = Enumerable.Range(1, 10);

// This will enumerate the sequence twice
foreach (int n in numbers) Console.WriteLine(n);
foreach (int n in numbers) Console.WriteLine(n);

使用 ToList()(或 ToArray())进行物化

IEnumerable numbers = Enumerable.Range(1, 10);
List materializedNumbers = numbers.ToList();   // materialized once

// Both loops now use the same in‑memory list
foreach (int n in materializedNumbers) Console.WriteLine(n);
foreach (int n in materializedNumbers) Console.WriteLine(n);

最佳实践

建议原因
及早物化 当你知道集合会被多次迭代时。确保原始源只枚举一次。
使用 ToList()ToArray()IEnumerable 转换为具体类型。提供快速的索引访问并避免重复执行查询。
为会被多次枚举的方法参数添加文档说明提醒调用者潜在的性能成本。
对代码进行性能分析,定位同一 IEnumerable 被重复枚举的热点。帮助你将优化工作集中在最关键的地方。
理解延迟执行(参见第 1 部分),以预测何时会发生枚举。防止意外对惰性查询进行多次遍历。

多次枚举特别有害的场景

  • 数据库查询 – 每次枚举可能重新执行 SQL 命令,导致多次往返。
  • 外部 API 调用 – 重新枚举会触发重复的网络请求。
  • 文件 I/O – 多次读取同一文件会浪费 I/O 带宽。
  • 昂贵的计算 – 重新计算复杂转换会降低性能。

快速检查清单

  • 我是否需要对集合进行多次迭代?
  • 如果需要,是否已经调用一次 ToList()/ToArray() 并复用结果?
  • 是否已经为会被多次枚举的参数添加了文档说明?
  • 是否已经对代码进行分析,确保没有隐藏的多次枚举?

进一步阅读

Back to Blog

相关文章

阅读更多 »

掌握在 .NET 中使用 NuGet 包

NuGet到底是什么?想象一下,NuGet是 .NET 版的 Amazon 或 Mercado Libre。你不会自己制造家具的每一颗螺丝,而是向商店购买它们。- Package...