foreach (Person criminal in people.Where(person => person.isCriminal)
{
// do something
}
我有这段代码,想知道它实际上是如何工作的。它是等效于嵌套在 foreach 迭代中的 if 语句,还是首先遍历人员列表并使用所选值重复循环?我想从效率的角度更多地了解这一点。
foreach (Person criminal in people)
{
if (criminal.isCriminal)
{
// do something
}
}
Where
使用延迟执行。
这意味着当您调用Where
时,过滤不会立即发生。相反,每次对返回值Where
调用GetEnumerator().MoveNext()
时,它都会检查序列中的下一个元素是否满足条件。如果没有,它将跳过此元素并检查下一个元素。当有一个元素满足条件时,它会停止前进,您可以使用Current
获取值。
基本上,这就像在foreach
循环中有一个if语句。
要了解发生的情况,您必须知道IEnumerables<T>
如何工作(因为 LINQ to Objects 始终适用于IEnumerables<T>
。IEnumerables<T>
返回一个实现迭代器的IEnumerator<T>
。这个迭代器是惰性的,即它总是一次只产生序列的一个元素。没有预先完成循环,除非您有需要它的OrderBy
或其他命令。
所以如果你有...
foreach (string name in source.Where(x => x.IsChecked).Select(x => x.Name)) {
Console.WriteLine(name);
}
。这将发生:foreach语句需要从Select
请求的第一个项目,而又需要来自Where
的一个项目,而又从源中检索一个项目。名字将打印到控制台。
然后foreach语句需要从Select
请求的第二个项目,而又需要来自Where
的一个项目,而又从源中检索一个项目。第二个名称将打印到控制台。
等等。
这意味着您的两个代码片段在逻辑上是等效的。
这取决于people
是什么。
如果people
是一个IEnumerable
对象(如集合,或使用yield
的方法的结果(,那么你问题中的两段代码确实是等价的。
一个幼稚的Where
可以实现为:
public static IEnumerable<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
// Error handling left out for simplicity.
foreach (TSource item in source)
{
if (predicate(item))
{
yield return item;
}
}
}
Enumerable
中的实际代码略有不同,以确保传递空source
或predicate
的错误立即发生,而不是在延迟执行时发生,并针对少数情况进行优化(例如source.Where(x => x.IsCriminal).Where(x => x.IsOnParole)
变成了等价的source.Where(x => x.IsCriminal && x.IsOnParole)
,因此迭代链中少了一个步骤(,但这是基本原则。
但是,如果people
是IQueryable
则情况有所不同,并且取决于所讨论的查询提供程序的详细信息。
最简单的可能性是查询提供程序无法对Where
执行任何特殊操作,因此它最终只执行上述操作,因为这仍然有效。
但查询提供程序通常可以执行其他操作。假设people
是实体框架中的一个DbSet<Person>
,与名为people
的数据库中的表相关联。如果您这样做:
foreach(var person in people)
{
DoSomething(person);
}
然后,实体框架将运行类似于以下内容的 SQL:
SELECT *
FROM people
然后为返回的每一行创建一个Person
对象。我们可以在即将实现Where
时进行相同的过滤,但我们也可以做得更好。
如果您这样做:
foreach (Person criminal in people.Where(person => person.isCriminal)
{
DoSomething(person);
}
然后,实体框架将运行类似于以下内容的 SQL:
SELECT *
FROM people
WHERE isCriminal = 1
这意味着决定返回哪些元素的逻辑在返回到 .NET 之前在数据库中完成。它允许使用索引来计算WHERE
,这可以更有效,但即使在最糟糕的情况下,没有有用的索引并且数据库必须进行完全扫描,它仍然意味着那些我们不关心的记录永远不会从数据库中报告回来,并且没有为它们创建对象只是为了再次被丢弃, 因此,性能差异可能是巨大的。
我想从效率的角度了解更多有关此的信息
希望您满意的是,没有您建议可能发生的双重传递,并且很高兴得知它比您建议的可能foreach … if
更有效。
裸露的foreach
和if
仍然会击败.Where()
IEnumerable
(但不是数据库源(,因为有一些开销需要Where
foreach
和if
没有,但它的程度只值得关心在非常热的路径中。通常,Where
可以对其效率有合理的信心。