【问题标题】:Using LINQ Where result in foreach: hidden if statement, double foreach?使用 LINQ 在哪里导致 foreach:隐藏的 if 语句,双 foreach?
【发布时间】:2017-07-04 15:22:10
【问题描述】:
foreach (Person criminal in people.Where(person => person.isCriminal)
{
    // do something
}

我有这段代码,想知道它实际上是如何工作的。它是否等同于嵌套在 foreach 迭代中的 if 语句,还是它首先遍历人员列表并使用选定的值重复循环?我想从效率的角度了解更多。

foreach (Person criminal in people)
{
    if (criminal.isCriminal)
    {
        // do something
    }
}

【问题讨论】:

  • 已按 IsCriminial 过滤的位置。因此,使用 where 子句的循环会更少
  • 如果你把整个逻辑放在 if 语句中,那么它的等价物
  • 是的。它会给你第一个罪犯然后调用你的逻辑,然后下一个罪犯然后调用你的逻辑等等。
  • 如果您想了解详细信息,您应该查看源代码。基本上,它几乎是一样的,你根本不应该担心性能。

标签: c# linq foreach


【解决方案1】:

Where 使用延迟执行。

这意味着当您调用Where 时不会立即进行过滤。相反,每次您在 Where 的返回值上调用 GetEnumerator().MoveNext() 时,它都会检查序列中的下一个元素是否满足条件。如果没有,它会跳过这个元素并检查下一个元素。当有满足条件的元素时,停止前进,可以使用Current获取值。

基本上,这就像在 foreach 循环中使用 if 语句。

【讨论】:

    【解决方案2】:

    要了解发生了什么,您必须知道 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 的一项,而Where 又从源中检索一项。名字被打印到控制台。

    然后,foreach 语句需要从Select 请求的第二项,而后者又需要来自Where 的一项,而Where 又从源中检索一项。第二个名字被打印到控制台。

    等等。

    这意味着你的两个代码片段在逻辑上是等价的。

    【讨论】:

      【解决方案3】:

      这取决于people 是什么。

      如果peopleIEnumerable 对象(如集合,或使用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 中的实际代码有点不同,以确保传递 null sourcepredicate 的错误立即发生而不是延迟执行,并针对少数情况进行优化(例如 @987654330 @ 变成了 source.Where(x =&gt; x.IsCriminal &amp;&amp; x.IsOnParole) 的等价物,这样在迭代链中就少了一个步骤),但这是基本原则。

      但是,如果 peopleIQueryable,则情况有所不同,具体取决于相关查询提供程序的详细信息。

      最简单的可能性是查询提供者不能对Where 做任何特殊的事情,所以它最终只能做上面的事情,因为这仍然有效。

      但查询提供者通常可以做其他事情。假设people 是实体框架中的DbSet&lt;Person&gt;,与数据库中名为people 的表相关联。如果你这样做:

      foreach(var person in people)
      {
        DoSomething(person);
      }
      

      然后 Entity Framework 将运行类似于以下的 SQL:

      SELECT *
      FROM people
      

      然后为返回的每一行创建一个Person 对象。我们可以在即将实现Where 时进行相同的过滤,但我们还可以做得更好。

      如果你这样做:

      foreach (Person criminal in people.Where(person => person.isCriminal)
      {
        DoSomething(person);
      }
      

      然后 Entity Framework 将运行类似于以下的 SQL:

      SELECT *
      FROM people
      WHERE isCriminal = 1
      

      这意味着决定返回哪些元素的逻辑在返回到 .NET 之前在数据库中完成。它允许在计算WHERE 时使用索引,这可以提高效率,但即使在没有有用索引并且数据库必须进行全面扫描的更糟糕的情况下,它仍然意味着我们不使用的那些记录't care about 永远不会从数据库中报告回来,并且没有为它们创建的对象只是为了再次被丢弃,因此性能差异可能是巨大的。

      我想从效率的角度了解更多

      希望您对不会发生您所建议的双重通过感到满意,并且很高兴得知它比您尽可能建议的 foreach … if 更有效。

      foreachif 仍然会在 IEnumerable 上击败 .Where()(但不是在数据库源上),因为 Whereforeachif 之间存在一些开销没有,但它的程度只有在非常热的道路上才值得关心。一般来说,Where 可以在对其效率有合理信心的情况下使用。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-06-01
        • 2013-07-01
        • 2017-09-27
        • 2014-05-28
        • 2015-10-16
        • 1970-01-01
        • 2014-09-02
        相关资源
        最近更新 更多