返回 IEnumberable

(一)基础篇

以下为完整正文内容。

正文

掌握本节,就可以在项目里用IEnumberable解决绝大部分问题了 IEnumerable 是 .NET 里最基础的数据序列接口,只有一个核心方法:GetEnumerator()。 实现这个接口的东西,就是在对外宣告“我能被 foreach 遍历”。 不管是数组 int[]、List<string>、还是 Dictionary<T>,它们都实现了 IEnumerable。 我们之所以能写 foreach(var item in list),就是因为 list 实现了这个接口。 foreach 在背后调用 GetEnumerator(),拿到一个“指针”,然后每次 MoveNext() 移动指针到下一个元素。 IEnumerable 的核心价值是“延迟执行”,也叫“惰性求值”。 如果不去遍历它,它就不干活;必须去遍历它,它才一个接一个地产出数据。 比如写 list.Where(x => x > 10),这一行执行完并不会立刻生成一个新列表。 它只是返回一个 IEnumerable 对象,里面装着一个“计划”而不是结果。 只有当你 foreach 它或者调用 .ToList() 的时候,筛选逻辑才真正执行。这和直接返回 List<T> 完全不同。 IEnumerable 每次遍历都会重新执行一遍逻辑,所以多次遍历就会多次执行。 比如: var filtered = list.Where(x => x > 10); // 这只是一个计划 var first = filtered.First(); // 执行一次筛选 var count = filtered.Count(); // 又执行一次筛选 上面 filtered 没有存储筛选结果,两次调用让 Where 里的判断逻辑跑了两次。如果数据量大或者里面有数据库调用,这就是性能陷阱。想避免就用 .ToList() 或 .ToArray() 提前固定结果。 看下面的代码,Where 里的 Console.WriteLine 会被执行几次? var nums = new List<int> {1, 2, 3, 4, 5}; var query = nums.Where(x => { Console.WriteLine($"检查{x}"); return x > 2; }); foreach (var n in query) { } foreach (var n in query) { } 答案: 10次 因为 foreach 每次遍历都重新执行筛选逻辑。第一次遍历 {1,2,3,4,5},输出 5 行检查语句。第二次遍历再次迭代原集合,又输出 5 行。所以总共打印 10 行。 想避免重复执行,就在第一次遍历前加 .ToList() 固化结果。 IEnumerable 是“可以被遍历” IEnumerator是“正在遍历的过程中”。 IEnumerable 返回一个 IEnumerator,IEnumerator 用 MoveNext() 和 Current 一步步往前走。 IEnumerator<int> enumerator = nums.GetEnumerator(); while (enumerator.MoveNext()) { Console.WriteLine(enumerator.Current); // 取当前 } 这就是 foreach 背后做的事。foreach 只是语法糖,帮你写了上面的代码。 IEnumerator 是一个只能向前不能后退的指针。你只能不断 MoveNext() 移到下一个,没有“退回上一个”的方法,也没有“跳到第几个”的方法。想把所有元素过一遍就得重新拿到一个新的 IEnumerator,再从头开始。 所以 IEnumerable 对应的模型是“单向流”,不是“随机访问”。如果需要按索引随便跳,那得用 IList 或数组。 IEnumerable 是只读的,它没有任何增删改的方法。 IEnumerable 的成员就一个 GetEnumerator()。没有 Add,没有 Remove,也没有索引器。所以你用 Where()、Select() 不会改变原集合,它们只是返回一个新的 IEnumerable,描述了如何基于原数据产出新数据。 这就是 LINQ 不会修改原集合的根本原因。