C# LINQ可枚举策略比较:Any、Count、SingleOrDefault

C# LINQ可枚举策略比较:Any、Count、SingleOrDefault,c#,linq,optimization,enumerable,reference-source,C#,Linq,Optimization,Enumerable,Reference Source,我们在的时候进行了一些反思,发现了迭代IEnumerable的不同策略,从而为我们提供了一些关于它的信息 任何 公共静态bool Any( 此IEnumerable源,Func谓词) { if(source==null)抛出错误.ArgumentNull(“source”); if(predicate==null)抛出Error.ArgumentNull(“predicate”); foreach(源中的TSource元素){ if(谓词(元素))返回true; } 返回false; } 公共静

我们在的时候进行了一些反思,发现了迭代
IEnumerable
的不同策略,从而为我们提供了一些关于它的信息

任何
公共静态bool Any(
此IEnumerable源,Func谓词)
{
if(source==null)抛出错误.ArgumentNull(“source”);
if(predicate==null)抛出Error.ArgumentNull(“predicate”);
foreach(源中的TSource元素){
if(谓词(元素))返回true;
}
返回false;
}
公共静态bool Any(此IEnumerable源)
{
if(source==null)抛出错误.ArgumentNull(“source”);
使用(IEnumerator e=source.GetEnumerator()){
如果(e.MoveNext())返回true;
}
返回false;
}
计数
公共静态整数计数(此IEnumerable源)
{
if(source==null)抛出错误.ArgumentNull(“source”);
ICollection collectionoft=源作为ICollection;
如果(collectionoft!=null)返回collectionoft.Count;
ICollection collection=源作为ICollection;
if(collection!=null)返回collection.Count;
整数计数=0;
使用(IEnumerator e=source.GetEnumerator()){
检查{
而(e.MoveNext())count++;
}
}
返回计数;
}
单缺省
publicstatictsourcesingleordefault(
此IEnumerable源,Func谓词)
{
if(source==null)抛出错误.ArgumentNull(“source”);
if(predicate==null)抛出Error.ArgumentNull(“predicate”);
TSource result=默认值(TSource);
长计数=0;
foreach(源中的TSource元素){
if(谓词(元素)){
结果=元素;
选中{count++;}
}
}
开关(计数){
案例0:返回默认值(TSource);
案例1:返回结果;
}
抛出错误。超过一个匹配();
}

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

  • Any:迭代枚举数,并提前退出
  • Count:迭代枚举数,除非ICollection调用Count
  • SingleOrDefault:迭代枚举数,不提前退出
一些意见:

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

  • 每种策略的优点和缺点是什么
  • LINQ的实现是这样的,有什么好的理由吗

    • 好吧,前两种方法是不言自明的,不是吗?它们的优化方式是尽快停止,并检查类型是否具有
      Count
      属性以避免循环

      但是with谓词确实有一个奇怪的实现。它可能会在第二个匹配项处停止,因为很明显必须抛出
      invalidoOperationException
      Single…
      ,而不是
      First…
      确保最多有一个项)。相反,它会检查每一项并统计匹配项。 服务器具有这种优化

      所以问题是:到底是什么?而且这似乎不会被修复,因为这只会降低出错情况下的性能。真不敢相信


      顺便说一句,同样的错误也存在于。

      如果你传递
      Any
      一个
      谓词
      ,你必须查看这些项以确定是否至少有一个匹配,所以我不知道如何为
      ICollection
      优化它,除非你只是在谈论使用
      for
      循环。在任何情况下,这对SO来说都是过于基于观点的。@steve16351但它可能在看到第二个匹配项时立即退出并出现错误。@juharr如果您对any(谓词)的看法是正确的,我还将包括any(),它可以针对ICollection进行优化。您正在查看.NET Framework源代码。NET核心有一些.NET Framework没有的优化。例如,
      SingleOrDefault
      和一个谓词在找到第二个匹配项时会发生短路,这不是一个急切与懒惰的问题。调用
      GetEnumerator
      可能需要创建一个新对象,但我猜任何额外的开销都不足以证明尝试强制转换的合理性。SingleOrDefault是最令人担忧的。谢谢你的澄清。计数检查可能是一个有争议的任何优化。事实上,他们没有费心检查任何()中的ICollection.Count,可能是因为它在这里不是一个优化。@dfhwze:优化将是如此微不足道,以至于它不是麦汁。还要考虑的是,有人创建了一个类型,实现了<代码> IcLogEng/<代码>的方式,调用<代码>计数>代码>属性将做大量工作。那么这个“优化”就可以做到这一点opposite@TimSchmelter尽管这违背了合同/一般期望,即房产只能做轻量工作,理想情况下可以立即归还。如果有人编写的代码中Count做了大量的工作,那是他们自己的问题,做了一些非常错误的事情。@ckuri可能是这样,但事实是属性经常起作用,而方法通常不起作用。微软不应该编写不必要的代码来捕获写得不好的代码,但这样做会带来尽可能少的麻烦。
      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;
      }
      
      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();
      }