【问题标题】:Count the items from a IEnumerable<T> without iterating?计算 IEnumerable<T> 中的项目而不进行迭代?
【发布时间】:2010-09-15 04:43:23
【问题描述】:
private IEnumerable<string> Tables
{
    get
    {
        yield return "Foo";
        yield return "Bar";
    }
}

假设我想对这些进行迭代并编写类似处理 #n of #m 的内容。

有没有一种方法可以在我的主迭代之前不进行迭代而找出 m 的值?

我希望我说清楚了。

【问题讨论】:

    标签: c# .net ienumerable


    【解决方案1】:

    IEnumerable 不支持这个。这是设计使然。 IEnumerable 使用惰性求值来获取您需要的元素。

    如果您想知道项目的数量而不对其进行迭代,您可以使用ICollection&lt;T&gt;,它有一个Count 属性。

    【讨论】:

    • 如果您不需要通过索引器访问列表,我更喜欢 ICollection 而不是 IList。
    • 我通常只是出于习惯而抓取 List 和 IList。但特别是如果您想自己实现它们,ICollection 更容易并且还具有 Count 属性。谢谢!
    • @Shimmy 你迭代并计算元素。或者您从为您执行此操作的 Linq 命名空间中调用 Count()。
    • 在正常情况下,将 IEnumerable 替换为 IList 就足够了吗?
    • @Helgi 因为 IEnumerable 是懒惰地评估的,所以你可以将它用于 IList 不能使用的东西。例如,您可以构建一个返回 IEnumerable 的函数,该函数枚举 Pi 的所有小数。只要您从不尝试对完整的结果进行 foreach,它就应该可以工作。您不能制作包含 Pi 的 IList。但这都是相当学术的。对于大多数正常用途,我完全同意。如果你需要 Count,你需要 IList。 :-)
    【解决方案2】:

    IEnumerable&lt;T&gt; 上的 System.Linq.Enumerable.Count 扩展方法具有以下实现:

    ICollection<T> c = source as ICollection<TSource>;
    if (c != null)
        return c.Count;
    
    int result = 0;
    using (IEnumerator<T> enumerator = source.GetEnumerator())
    {
        while (enumerator.MoveNext())
            result++;
    }
    return result;
    

    因此它尝试转换为具有Count 属性的ICollection&lt;T&gt;,并尽可能使用它。否则它会迭代。

    因此,最好的办法是在 IEnumerable&lt;T&gt; 对象上使用 Count() 扩展方法,因为这样您将获得最佳性能。

    【讨论】:

    • 非常有趣的是它首先尝试转换为ICollection&lt;T&gt;
    • @OscarMederos Enumerable 中的大多数扩展方法都对各种类型的序列进行了优化,如果可以的话,它们会使用更便宜的方式。
    • 提到的扩展自 .Net 3.5 起可用,并记录在 MSDN
    • @Jaider - 比这稍微复杂一些。 IEnumerable&lt;T&gt; 继承 IDisposable 允许 using 语句自动处理它。 IEnumerable 没有。因此,如果您以任何方式调用GetEnumerator,您应该以var d = e as IDisposable; if (d != null) d.Dispose(); 结束
    • 遗憾的是 .NET 在 IEnumerable 和 ICollection 之间没有中间接口,用于具有预先已知计数的 IEnumerables(但不需要 ICollection 的其他功能)。也没有办法知道,给定一个任意的 IEnumerable,并且需要在算法的早期知道“Count”,调用 IEnumerable.Count 是否会更便宜,从而第二次迭代收集,或者收集到一个列表中一次,以便您有一个计数,然后使用该列表。更糟糕的是,可能存在迭代两次的“副作用”。另一方面,枚举可能有很多元素
    【解决方案3】:

    只是添加一些额外的信息:

    Count() 扩展并不总是迭代。考虑 Linq to Sql,其中计数进入数据库,但不是带回所有行,而是发出 Sql Count() 命令并返回该结果。

    此外,编译器(或运行时)足够聪明,它会调用对象Count() 方法(如果有的话)。所以它不是像其他响应者所说的那样,完全无知并且总是为了计算元素而迭代。

    在许多情况下,程序员只是使用Any() 扩展方法检查if( enumerable.Count != 0 ),因为if( enumerable.Any() ) 使用linq 的惰性求值效率要高得多,因为一旦确定存在任何元素,它就会短路.它也更具可读性

    【讨论】:

    • 关于集合和数组。如果您碰巧使用集合,请使用 .Count 属性,因为它始终知道它的大小。查询collection.Count 时没有额外的计算,它只是返回已知的计数。据我所知,Array.length 也是如此。但是,.Any() 使用using (IEnumerator&lt;TSource&gt; enumerator = source.GetEnumerator()) 获取源的枚举数,如果可以使用enumerator.MoveNext(),则返回true。对于集合:if(collection.Count &gt; 0),数组:if(array.length &gt; 0),对可枚举执行 if(collection.Any())
    • 第一点并不完全正确... LINQ to SQL 使用 this extension methodthis one 不同。如果使用第二个,计数是在内存中执行的,而不是作为 SQL 函数
    • @AlexFoxGill 是正确的。如果您将 IQueryably&lt;T&gt; 显式转换为 IEnumerable&lt;T&gt;,则不会发出 sql 计数。当我写这篇文章时,Linq to Sql 是新的;我想我只是在推动使用Any(),因为对于可枚举、集合和 sql,它更加高效和可读(通常)。感谢您改进答案。
    【解决方案4】:

    我的一个朋友有一系列博客文章,说明了为什么你不能这样做。他创建了一个返回 IEnumerable 的函数,其中每次迭代都返回下一个素数,一直到ulong.MaxValue,并且在您要求之前不会计算下一个项目。快速的流行问题:返回了多少项目?

    这里是帖子,但有点长:

    1. Beyond Loops(提供其他帖子中使用的初始 EnumerableUtility 类)
    2. Applications of Iterate(初步实现)
    3. Crazy Extention Methods: ToLazyList(性能优化)

    【讨论】:

    • 我真的很希望 MS 已经定义了一种方法来要求可枚举对象描述他们对自己的能力(“什么都不知道”是一个有效的答案)。任何可枚举者都应该很难回答诸如“你知道自己是有限的”、“你知道自己是有限的、少于 N 个元素的吗”和“你知道自己是无限的”这样的问题,因为任何可枚举者都可以合法地(如果没有帮助)对他们所有人回答“不”。如果有提出此类问题的标准方法,那么枚举器返回无穷无尽的序列会更安全......
    • ...(因为他们可以说他们这样做了),并且代码假设不声称返回无限序列的枚举器可能是有界的。请注意,包括提出此类问题的方法(以尽量减少样板文件,可能有一个属性返回 EnumerableFeatures 对象)不需要枚举器做任何困难的事情,但能够提出此类问题(以及其他一些问题,例如“可以你承诺总是返回相同的项目序列”、“你能安全地接触到不应该改变你的基础集合的代码吗”等)将非常有用。
    • 那会很酷,但我现在确定它与迭代器块的融合程度如何。您需要某种特殊的“收益选项”或其他东西。或者也许使用属性来装饰迭代器方法。
    • 在没有任何其他特殊声明的情况下,迭代器块可以简单地报告它们对返回的序列一无所知,尽管如果 IEnumerator 或 MS 认可的后继者(可以由GetEnumerator 实现知道它的存在)是为了支持额外的信息,C# 可能会得到一个set yield options 声明或类似的东西来支持它。如果设计得当,IEnhancedEnumerator 可以通过消除大量“防御性”ToArrayToList 调用来使 LINQ 之类的东西更有用,尤其是...
    • ...如果像Enumerable.Concat 这样的东西被用来将一个对自身了解很多的大型集合与一个不了解的小型集合结合起来。
    【解决方案5】:

    或者,您可以执行以下操作:

    Tables.ToList<string>().Count;
    

    【讨论】:

      【解决方案6】:

      IEnumerable 不迭代就无法计数。

      在“正常”情况下,实现 IEnumerable 或 IEnumerable 的类(例如 List)可以通过返回 List.Count 属性来实现 Count 方法。但是,Count 方法实际上并不是在 IEnumerable 或 IEnumerable 接口上定义的方法。 (事实上​​,唯一的一个是 GetEnumerator。)这意味着无法为其提供特定于类的实现。

      相反,Count 是一个扩展方法,定义在静态类 Enumerable 上。这意味着它可以在 IEnumerable 派生类的任何实例上调用,无论该类的实现如何。但这也意味着它是在任何这些类之外的一个地方实现的。这当然意味着它必须以完全独立于这些类的内部的方式实现。进行计数的唯一方法是通过迭代。

      【讨论】:

      • 这是一个很好的点,除非您进行迭代,否则无法计数。计数功能与实现 IEnumerable....那些只有在你知道类型之后。我个人觉得这个帖子非常有用,所以也感谢克里斯的回复。
      • 根据Daniel's answer,这个答案并不完全正确:实现确实检查对象是否实现了“ICollection”,它有一个“Count”字段。如果是这样,它会使用它。 (我不知道它在 08 年是否那么聪明。)
      【解决方案7】:

      不,一般来说不是。使用 enumerables 的一点是枚举中的实际对象集是未知的(预先知道,甚至根本不知道)。

      【讨论】:

      • 您提出的重要一点是,即使您获得了该 IEnumerable 对象,您也必须查看是否可以强制转换它以确定它是什么类型。对于那些试图在我的代码中像我一样使用更多 IEnumerable 的人来说,这是非常重要的一点。
      【解决方案8】:

      您可以使用 System.Linq。

      using System;
      using System.Collections.Generic;
      using System.Linq;
      
      public class Test
      {
          private IEnumerable<string> Tables
          {
              get {
                   yield return "Foo";
                   yield return "Bar";
               }
          }
      
          static void Main()
          {
              var x = new Test();
              Console.WriteLine(x.Tables.Count());
          }
      }
      

      你会得到结果'2'。

      【讨论】:

      • 这不适用于非泛型变体 IEnumerable(没有类型说明符)
      • 如果你有一个 IEnumerable,.Count 的实现是枚举所有的项目。 (ICollection 不同)。 OP 问题显然是“没有迭代”
      【解决方案9】:

      我认为最简单的方法

      Enumerable.Count<TSource>(IEnumerable<TSource> source)
      

      参考:system.linq.enumerable

      【讨论】:

      • 问题是“计算 IEnumerable 中的项目而不进行迭代?”这是怎么回答的?
      • 作者的意思是不迭代他们编写的代码还是根本不迭代任何地方。我认为这是一个有效的答案。
      【解决方案10】:

      除了您的直接问题(已彻底否定回答)之外,如果您希望在处理可枚举时报告进度,您可能需要查看我的博客文章 Reporting Progress During Linq Queries

      它可以让你这样做:

      BackgroundWorker worker = new BackgroundWorker();
      worker.WorkerReportsProgress = true;
      worker.DoWork += (sender, e) =>
            {
                // pretend we have a collection of 
                // items to process
                var items = 1.To(1000);
                items
                    .WithProgressReporting(progress => worker.ReportProgress(progress))
                    .ForEach(item => Thread.Sleep(10)); // simulate some real work
            };
      

      【讨论】:

        【解决方案11】:

        我在方法中使用了这种方式来检查传入的IEnumberable内容

        if( iEnum.Cast<Object>().Count() > 0) 
        {
        
        }
        

        在这样的方法中:

        GetDataTable(IEnumberable iEnum)
        {  
            if (iEnum != null && iEnum.Cast<Object>().Count() > 0) //--- proceed further
        
        }
        

        【讨论】:

        • 为什么要这样做? “计数”可能很昂贵,因此给定一个 IEnumerable,将所需的任何输出初始化为适当的默认值,然后开始迭代“iEnum”会更便宜。选择默认值,以使从不执行循环的空“iEnum”最终得到有效结果。有时,这意味着添加一个布尔标志来了解循环是否已执行。诚然,这很笨拙,但依靠“伯爵”似乎是不明智的。如果需要这个标志,代码如下:bool hasContents = false; if (iEnum != null) foreach (object ob in iEnum) { hasContents = true; ... your code per ob ... }.
        • ... 添加特殊代码也很容易,这些代码应该只在第一次完成,或者只在迭代其他而不是第一次完成:`... { if (!hasContents) { hasContents = true; ..一次代码..; } else { ..code for all but first time..} ...}" 诚然,这比你的简单方法更笨拙,其中一次性代码将在你的 if 中,在循环之前,但如果 " .Count()" 可能是一个问题,那么这就是要走的路。
        【解决方案12】:

        这取决于 .Net 的版本和 IEnumerable 对象的实现。 微软已经修复了 IEnumerable.Count 方法来检查实现,并使用 ICollection.Count 或 ICollection.Count,详见此处https://connect.microsoft.com/VisualStudio/feedback/details/454130

        下面是来自 Ildasm 的 System.Core 的 MSIL,System.Linq 驻留在其中。

        .method public hidebysig static int32  Count<TSource>(class 
        
        [mscorlib]System.Collections.Generic.IEnumerable`1<!!TSource> source) cil managed
        {
          .custom instance void System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) 
          // Code size       85 (0x55)
          .maxstack  2
          .locals init (class [mscorlib]System.Collections.Generic.ICollection`1<!!TSource> V_0,
                   class [mscorlib]System.Collections.ICollection V_1,
                   int32 V_2,
                   class [mscorlib]System.Collections.Generic.IEnumerator`1<!!TSource> V_3)
          IL_0000:  ldarg.0
          IL_0001:  brtrue.s   IL_000e
          IL_0003:  ldstr      "source"
          IL_0008:  call       class [mscorlib]System.Exception System.Linq.Error::ArgumentNull(string)
          IL_000d:  throw
          IL_000e:  ldarg.0
          IL_000f:  isinst     class [mscorlib]System.Collections.Generic.ICollection`1<!!TSource>
          IL_0014:  stloc.0
          IL_0015:  ldloc.0
          IL_0016:  brfalse.s  IL_001f
          IL_0018:  ldloc.0
          IL_0019:  callvirt   instance int32 class [mscorlib]System.Collections.Generic.ICollection`1<!!TSource>::get_Count()
          IL_001e:  ret
          IL_001f:  ldarg.0
          IL_0020:  isinst     [mscorlib]System.Collections.ICollection
          IL_0025:  stloc.1
          IL_0026:  ldloc.1
          IL_0027:  brfalse.s  IL_0030
          IL_0029:  ldloc.1
          IL_002a:  callvirt   instance int32 [mscorlib]System.Collections.ICollection::get_Count()
          IL_002f:  ret
          IL_0030:  ldc.i4.0
          IL_0031:  stloc.2
          IL_0032:  ldarg.0
          IL_0033:  callvirt   instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> class [mscorlib]System.Collections.Generic.IEnumerable`1<!!TSource>::GetEnumerator()
          IL_0038:  stloc.3
          .try
          {
            IL_0039:  br.s       IL_003f
            IL_003b:  ldloc.2
            IL_003c:  ldc.i4.1
            IL_003d:  add.ovf
            IL_003e:  stloc.2
            IL_003f:  ldloc.3
            IL_0040:  callvirt   instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
            IL_0045:  brtrue.s   IL_003b
            IL_0047:  leave.s    IL_0053
          }  // end .try
          finally
          {
            IL_0049:  ldloc.3
            IL_004a:  brfalse.s  IL_0052
            IL_004c:  ldloc.3
            IL_004d:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
            IL_0052:  endfinally
          }  // end handler
          IL_0053:  ldloc.2
          IL_0054:  ret
        } // end of method Enumerable::Count
        

        【讨论】:

          【解决方案13】:

          IEnumerable.Count() 函数的结果可能是错误的。这是一个非常简单的测试示例:

          using System;
          using System.Collections.Generic;
          using System.Linq;
          using System.Collections;
          
          namespace Test
          {
            class Program
            {
              static void Main(string[] args)
              {
                var test = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 };
                var result = test.Split(7);
                int cnt = 0;
          
                foreach (IEnumerable<int> chunk in result)
                {
                  cnt = chunk.Count();
                  Console.WriteLine(cnt);
                }
                cnt = result.Count();
                Console.WriteLine(cnt);
                Console.ReadLine();
              }
            }
          
            static class LinqExt
            {
              public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> source, int chunkLength)
              {
                if (chunkLength <= 0)
                  throw new ArgumentOutOfRangeException("chunkLength", "chunkLength must be greater than 0");
          
                IEnumerable<T> result = null;
                using (IEnumerator<T> enumerator = source.GetEnumerator())
                {
                  while (enumerator.MoveNext())
                  {
                    result = GetChunk(enumerator, chunkLength);
                    yield return result;
                  }
                }
              }
          
              static IEnumerable<T> GetChunk<T>(IEnumerator<T> source, int chunkLength)
              {
                int x = chunkLength;
                do
                  yield return source.Current;
                while (--x > 0 && source.MoveNext());
              }
            }
          }
          

          结果必须是 (7,7,3,3) 但实际结果是 (7,7,3,17)

          【讨论】:

            【解决方案14】:

            .NET 6 的 LINQ 中有一个新方法 观看https://www.youtube.com/watch?v=sIXKpyhxHR8

            Tables.TryGetNonEnumeratedCount(out var count)
            

            【讨论】:

              【解决方案15】:

              这是关于lazy evaluationdeferred execution 的精彩讨论。基本上,您必须具体化列表才能获得该值。

              【讨论】:

                【解决方案16】:

                我发现最好的方法是将其转换为列表来计数。

                IEnumerable<T> enumList = ReturnFromSomeFunction();
                
                int count = new List<T>(enumList).Count;
                

                【讨论】:

                  【解决方案17】:

                  简化所有答案。

                  IEnumerable 没有 Count 函数或属性。为此,您可以存储 count 变量(例如使用 foreach)或使用 Linq 求解以获取计数。

                  如果您有:

                  IEnumerable 产品

                  那么:

                  声明:“使用 System.Linq;”

                  计数:

                  products.ToList().Count

                  【讨论】:

                    【解决方案18】:

                    我建议调用 ToList。是的,您正在尽早进行枚举,但您仍然可以访问您的项目列表。

                    【讨论】:

                      【解决方案19】:

                      它可能不会产生最佳性能,但您可以使用 LINQ 计算 IEnumerable 中的元素:

                      public int GetEnumerableCount(IEnumerable Enumerable)
                      {
                          return (from object Item in Enumerable
                                  select Item).Count();
                      }
                      

                      【讨论】:

                      • 结果与简单地执行“`return Enumerable.Count();”有何不同?
                      • 好问题,或者它实际上是这个 Stackoverflow 问题的答案。
                      【解决方案20】:

                      我使用IEnum&lt;string&gt;.ToArray&lt;string&gt;().Length,它工作正常。

                      【讨论】:

                      • 这应该可以正常工作。 IEnumerator.ToArray.Length
                      • 为什么要这样做,而不是Daniel's highly-upvoted answer written three years earlier than yours、“IEnum&lt;string&gt;.Count();”中已经给出的更简洁、性能更快的解决方案?
                      • 你是对的。不知怎的,我忽略了丹尼尔的回答,可能是因为他引用了实现,我认为他实现了扩展方法,我正在寻找代码更少的解决方案。
                      • 处理我的错误答案最被接受的方式是什么?我应该删除它吗?
                      【解决方案21】:

                      如果我有字符串列表,我会使用这样的代码:

                      ((IList<string>)Table).Count
                      

                      【讨论】:

                      • 希望底层连接可以转换为IList&lt;string&gt; 然后...(不,我不是在谈论string 部分,只是不是所有IEnumerable&lt;T&gt; 都是由实现 IList&lt;T&gt;) 的类型支持
                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 2015-05-27
                      • 1970-01-01
                      • 2020-08-22
                      • 2019-02-27
                      • 2014-06-11
                      • 2014-06-25
                      相关资源
                      最近更新 更多