【问题标题】:LINQ Enumerable strategy comparison: Any, Count, SingleOrDefaultLINQ 可枚举策略比较:Any、Count、SingleOrDefault
【发布时间】:2019-12-04 23:46:17
【问题描述】:

我们对Code ReviewLINQ Enumerable 进行了一些反省,并发现了迭代IEnumerable<TSource> 以向我们提供一些有关它的信息的不同策略。

任何

public static bool Any<TSource>(
     this IEnumerable<TSource> source, Func<TSource, bool> predicate) 
{
     if (source == null) throw Error.ArgumentNull("source");
     if (predicate == null) throw Error.ArgumentNull("predicate");
     foreach (TSource element in source) {
         if (predicate(element)) return true;
     }
     return false;
 }

 public static bool Any<TSource>(this IEnumerable<TSource> source)
 {
      if (source == null) throw Error.ArgumentNull("source");
      using (IEnumerator<TSource> e = source.GetEnumerator()) {
          if (e.MoveNext()) return true;
      }
      return false;
  }

计数

public static int Count<TSource>(this IEnumerable<TSource> source) 
{
    if (source == null) throw Error.ArgumentNull("source");
    ICollection<TSource> collectionoft = source as ICollection<TSource>;
    if (collectionoft != null) return collectionoft.Count;
    ICollection collection = source as ICollection;
    if (collection != null) return collection.Count;
    int count = 0;
    using (IEnumerator<TSource> e = source.GetEnumerator()) {
        checked {
            while (e.MoveNext()) count++;
        }
    }
    return count;
}

SingleOrDefault

public static TSource SingleOrDefault<TSource>(
    this IEnumerable<TSource> source, Func<TSource, bool> predicate) 
{
    if (source == null) throw Error.ArgumentNull("source");
    if (predicate == null) throw Error.ArgumentNull("predicate");
    TSource result = default(TSource);
    long count = 0;
    foreach (TSource element in source) {
        if (predicate(element)) {
            result = element;
            checked { count++; }
        }
    }
    switch (count) {
        case 0: return default(TSource);
        case 1: return result;
    }
    throw Error.MoreThanOneMatch();
}

如您所见,使用了 3 种不同的策略。

  • Any:迭代枚举器,提前退出
  • Count:迭代枚举器,除非ICollection,调用Count
  • SingleOrDefault:迭代枚举器,不提前退出

一些观察:

  • “Any()”也可以针对 ICollection 进行优化
  • “SingleOrDefault”也可以进行优化以提前退出
  • 这些方法都不关心 IReadOnlyCollection,即使它也有一个属性 'Count'

问题

  • 这些策略的优缺点是什么?
  • LINQ 的实现是这样的吗?

【问题讨论】:

  • 如果您通过Anypredicate,您必须查看项目以确定您是否至少有一个匹配项,所以我看不出如何针对@987654329 进行优化@ 除非您只是在谈论使用 for 循环。无论如何,这对于 SO 来说太基于意见了。
  • @steve16351 但它可能会在看到第二个匹配项时立即退出并出现错误。
  • @juharr 你对 Any(predicate) 是正确的,我还将包括 Any(),它可以针对 ICollection 进行优化
  • 您正在查看 .NET Framework 源代码。 .NET Core 有一些 .NET Framework 没有的优化。例如,SingleOrDefault 带有谓词 does 一旦找到第二个匹配项就会短路。
  • 这不是一个急切与懒惰的问题。调用 GetEnumerator 可能需要创建一个新对象,但我猜任何额外的开销都不足以证明尝试强制转换是合理的。

标签: c# linq optimization enumerable reference-source


【解决方案1】:

嗯,前两种方法是不言自明的,不是吗?它们以一种尽快停止的方式进行了优化,并检查该类型是否具有Count 属性以避免循环。

但是SingleOrDefault with predicate 确实有一个奇怪的实现。它可能会停在第二个匹配项,因为从那时起很明显必须抛出 InvalidOperationExceptionSingle... 而不是 First... 确保最多有 1 个项目)。但相反,它会检查每个项目并计算匹配项。 version without predicate 有这个优化。

所以问题是:到底是什么?而且似乎is really a bug 不会被修复,因为它只会降低错误情况下的性能。我不敢相信。

顺便说一句,Enumerable.Single 中也存在同样的错误。

【讨论】:

  • SingleOrDefault 是最令人担忧的。感谢您的澄清。 Count 检查可能是对 Any 的一个有争议的优化。
  • 他们没有费心检查 Any() 中的 ICollection.Count 的事实可能是因为它不会在这里进行优化。
  • @dfhwze:优化是如此微不足道,以至于它不是麦汁。还要考虑这样一种情况,有人创建了一个实现ICollection&lt;TSource&gt; 的类型,调用Count 属性会做很多工作。那么这种“优化”会适得其反
  • @TimSchmelter 虽然这违背了合同/一般预期,即房产应该只做轻松的工作并理想地立即返回。如果有人编写代码时 Count 做了很多工作,那是他们自己做错了。
  • @ckuri 也许可以,但事实是属性通常有效而方法通常无效。 Microsoft 不应该编写不必要地捕获编写不佳的代码的代码,但这样做会尽可能减少麻烦。
猜你喜欢
  • 2023-01-18
  • 1970-01-01
  • 2012-05-04
  • 1970-01-01
  • 2010-09-22
  • 2023-03-09
  • 2023-03-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多