使用 LINQ 哪里导致 foreach:隐藏 if 语句,双 foreach?


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中的实际代码略有不同,以确保传递空sourcepredicate的错误立即发生,而不是在延迟执行时发生,并针对少数情况进行优化(例如source.Where(x => x.IsCriminal).Where(x => x.IsOnParole)变成了等价的source.Where(x => x.IsCriminal && x.IsOnParole),因此迭代链中少了一个步骤(,但这是基本原则。

但是,如果peopleIQueryable则情况有所不同,并且取决于所讨论的查询提供程序的详细信息。

最简单的可能性是查询提供程序无法对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更有效。

裸露的foreachif仍然会击败.Where()IEnumerable(但不是数据库源(,因为有一些开销需要Whereforeachif没有,但它的程度只值得关心在非常热的路径中。通常,Where可以对其效率有合理的信心。

最新更新