在 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;
}
但对我来说,这里使用“foreach”似乎会枚举我们的集合,所以在我们有 collection.Where(x => ...).Where(x => ...).ToList() 的情况下 -枚举将发生 3 次:第一次在第一个Where中,第二次在第二个Where中,最后一次在ToList()中。但我在想,LINQ 链的整个思想就是只枚举一次。 我是否弄错了,它只会被枚举一次?请指导我
看起来在这里使用“foreach”将枚举我们的集合,
它确实看起来像这样,但实际情况并非如此。相反,yield
关键字告诉编译器应该对方法执行转换,以便它返回一个
IEnumerable
对象,该对象仅知道如何迭代集合,而无需实际执行该工作。在您实际使用并迭代此返回的 IEnumerable 之前,根本不会发生枚举。 编译器转换以
可堆栈的方式发生,因此即使多次调用.Where()
(如问题中的
collection.Where(x => ...).Where(x => ...).ToList()
表达式),仅发生一次枚举。您也可以混合使用其他 linq 操作,例如
.Select()
、
.Any()
、
.Aggregate()
等。这可以使编写 linq 代码
极其高效。缺点是每次使用此方法都会导致枚举对象的内存分配,因此了解内存受限与 CPU 受限的情况很有用,并且通常值得将其中一些结合起来,因此 .Where(x => ...).Where(x => )
改为
.Where(x => ... && ...)
。最后,虽然也有例外,但您应该
尽可能避免调用 ToList()
ToArray()
)。相反,对于方法参数、返回类型和变量声明,请使用
IEnumerable<T>
而不是
List<T>
或
T[]
。在许多情况下,最终的
foreach
循环、数据绑定或其他机制足以强制最终枚举,这样您实际上不需要构造列表或数组,并且这些其他选项更有效 而不是实际构建一个可能不需要的列表。