2008-10-03 44 views
251
private IEnumerable<string> Tables 
{ 
    get 
    { 
     yield return "Foo"; 
     yield return "Bar"; 
    } 
} 

比方說,我想對這些進行迭代,然後編寫#m處理#n。從IEnumerable <T>中統計項目而不進行迭代?

有沒有一種方法,我可以找出m的價值,而無需在我的主迭代之前進行迭代?

我希望我明確自己。

回答

254

IEnumerable不支持這一點。這是設計。 IEnumerable使用懶惰評估來獲取您在需要之前請求的元素。

如果您想知道沒有迭代它們的物品數量,您可以使用ICollection<T>,它具有Count屬性。

+33

如果您不需要通過索引器訪問列表,我希望ICollection優先於IList。 – 2008-10-03 21:17:20

+3

我通常只是抓住List和IList的習慣。但是特別是如果你想自己實現它們,ICollection更容易,同時也具有Count屬性。謝謝! – Mendelt 2008-10-03 22:05:20

+0

那麼當我有給定的IEnumerable時,如何檢查計數? – Shimmy 2009-11-30 23:40:31

7

不,一般不會。使用枚舉數的一個要點是枚舉中的實際對象集並不知道(事先或甚至完全不知道)。

+0

您帶來了最重要的一點是,即使你得到的IEnumerable的對象,你必須看到,如果你能投它弄清楚它是什麼類型。對於那些試圖在我的代碼中使用更多IEnumerable的人來說,這是非常重要的一點。 – PositiveGuy 2010-08-18 15:35:34

0

編號

您是否看到在您編寫的代碼中的任何位置可用的信息?

您可能會爭辯說,編譯器可以「看到」只有兩個,但這意味着它需要分析每個迭代器方法,只是針對該特定的病例。即使它確實如此,考慮到IEnumerable的限制,你會如何閱讀它?

9

我的一位朋友有一系列的博客文章,提供了爲什麼你不能做到這一點的插圖。他創建的函數返回一個IEnumerable,其中每次迭代都會返回下一個素數,一直到ulong.MaxValue,並且下一個項目不會計算,直到您請求爲止。快速,流行的問題:有多少物品被退回?

這裏有崗位,但他們那種長:

  1. Beyond Loops(提供在其他職位使用的初始EnumerableUtility類)
  2. Applications of Iterate(初始執行)
  3. Crazy Extention Methods: ToLazyList(性能優化)
9

IEnumerable無法迭代計數。

在「正常」的情況下,將對於如List <Ť>實現了IEnumerable或IEnumerable的<Ť>類,是可能的,通過返回列表<Ť> .Count之間的屬性來實現的計數方法。但是,Count方法實際上並不是在IEnumerable <T>或IEnumerable接口上定義的方法。 (實際上,唯一一個是GetEnumerator。)這意味着無法爲它提供特定於類的實現。

相反,它是一個擴展方法,定義在靜態類Enumerable上。這意味着可以在任何IEnumerable派生類的實例上調用它,而不管該類的實現如何。但這也意味着它在任何一個類別的外部實施。這當然意味着它必須以完全獨立於這些類的內部的方式來實現。唯一這種做計數的方法是通過迭代。

69

只是增加了額外的一些信息:

Count()擴展並不總是迭代。考慮Linq to Sql,其中計數到達數據庫,但不是將所有行都帶回來,而是發出Sql Count()命令並返回該結果。

此外,編譯器(或運行時)足夠聰明,如果它有一個,它將調用對象Count()方法。所以它的而不是正如其他響應者所說,完全無知並且總是迭代以計算元素。

在許多情況下程序員使用Any()擴展方法,因爲它可以短路,一旦它能夠確定存在任何元件如if(enumerable.Any())更爲高效與LINQ的懶惰評價只是檢查if(enumerable.Count != 0)。它也更具可讀性

0

我建議調用ToList。是的,您正在儘早進行枚舉,但您仍然可以訪問您的項目列表。

7

或者,你可以做到以下幾點:

Tables.ToList<string>().Count; 
165

System.Linq.Enumerable.Count擴展方法上IEnumerable<T>具有以下實現:

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; 

所以它試圖強制轉換爲ICollection<T>,其中有一個Count財產,並使用該如果可能的話。否則它迭代。

所以你最好的選擇是在你的IEnumerable<T>對象上使用Count()擴展方法,因爲你將以這種方式獲得最佳性能。

5

超出您的直接問題(已經徹底回答否定),如果您希望在處理枚舉數時報告進度,您可能需要查看我的博客文章Reporting Progress During Linq Queries

它可以讓你這樣做:在IEnumerable.Count()函數的

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 
     }; 
2

結果可能是錯誤的。這是一個非常簡單的樣品進行測試:

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)

2

這取決於哪個版本.Net和您的IEnumerable對象的實現。 微軟已經修復了IEnumerable.Count方法來檢查落實,並使用ICollection.Count或ICollection的< TSource> .Count中,看到這裏的細節https://connect.microsoft.com/VisualStudio/feedback/details/454130

而且下面是MSIL從ILDASM的System.Core程序,在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 
1

有一個快速計數的唯一方法是當原始集合有一個索引器(如數組)。爲了創建通用代碼的最低要求,你可以使用IEnumerable的,但如果你需要的數量也就那麼我的首選方法是使用這個接口:如果您原來的集合不具有任何索引


    public interface IEnumAndCount<out T> : IEnumerable<T> 
    { 
     int Count { get; } 
    } 

,你的計數實現可以遍歷集合,並具有已知的性能O(n)。

如果你不想使用類似於IEnumAndCount的東西,你最好的選擇是和Linq.Count一起去看Daniel Earwicker給出的問題。

祝你好運!

-2

我使用IEnum<string>.ToArray<string>().Length它工作正常。

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'。

3

我用這樣的方式方法裏面來檢查IEnumberable內容

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

} 

傳遞內部的方法是這樣的:

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

} 
0

它可能不會產生最佳性能,但可以使用LINQ來計算IEnumerable中的元素:

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

你不能沒有inte至少是至少一次迭代。這將工作。

using System.Linq; 
...  
Tables.Count(); 
相關問題