jon*_*bee 4 c# linq ienumerable
在 Joseph Albahari 的《C# in a Nutshell》一书中,据说 .Where 的实现如下:
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource,bool> predicate)
{
foreach (TSource element in source)
if (predicate (element))
yield return element;
}
Run Code Online (Sandbox Code Playgroud)
但对我来说,这里使用“foreach”似乎会枚举我们的集合,因此在我们有 collection.Where(x => ...).Where(x => ...).ToList() 的情况下 - 枚举将发生 3 次:第一次在第一个Where中,第二次在第二个Where中,最后一次在ToList()中。但我在想,LINQ 链的整个思想就是只枚举一次。我是否弄错了,它只会被枚举一次?请指导我
看起来在这里使用“foreach”将枚举我们的集合,
看起来确实如此,但实际情况并非如此。相反,yield关键字告诉编译器应该对该方法执行转换IEnumerator,以便它创建一个只知道如何迭代集合的对象,而不是立即实际执行该工作。在您实际使用并迭代这个创建的对象之前,根本不会发生枚举。
编译器转换以可堆栈的方式发生,因此即使多次调用.Where(),如collection.Where(x => ...).Where(x => ...).ToList()问题中的表达式所示,也只会发生一次枚举。您也可以混合使用其他 linq 操作,例如、.Select()、等。.Any().Aggregate()
因此,在问题的示例代码中,唯一的枚举是作为最终调用的一部分发生的.ToList()。
这可以使编写 linq 代码变得极其高效。缺点是每次使用此方法都会导致枚举对象的内存分配,因此了解内存受限与 cpu 受限时很有用(并且不要忽略分配的 GC 成本)。通常,将其中一些组合起来是值得的,因此.Where(x => ...).Where(x => ...)变成了.Where(x => ... && ...). 我仍然希望 C# 团队能够提出一些神奇的“ValueEnumerable”,就像他们对元组、任务和跨度所做的那样,可以减少这些分配。
最后,虽然也有例外,但通常您应该尽可能避免调用ToList()(或)。ToArray()相反,对于方法参数、返回类型和变量声明,更喜欢IEnumerable<T>使用List<T>or 。T[]在许多情况下,最终循环、数据绑定或其他机制足以foreach强制最终枚举,这样您实际上不需要构造列表或数组,并且这些其他选项比实际构建更有效(并为其分配内存)可能不需要的列表,特别是对于无法预先推断最终大小且足够大以List<T>多次命中 的加倍算法的序列。