反復せずにIEnumerable <T>から項目をカウントしますか?


317
private IEnumerable<string> Tables
{
    get
    {
        yield return "Foo";
        yield return "Bar";
    }
}

それらを繰り返して、#mの#nを処理するようなものを書きたいとしましょう。

主な反復の前に反復せずにmの値を見つける方法はありますか?

はっきりさせてほしい。

回答:


338

IEnumerableこれはサポートされていません。これは仕様によるものです。IEnumerable遅延評価を使用して、必要になる直前に要求する要素を取得します。

繰り返し使用せずに使用できる項目の数を知りたい場合ICollection<T>は、Countプロパティがあります。


38
インデクサーでリストにアクセスする必要がない場合は、IListよりもICollectionを優先します。
マイケルメドウズ

3
私は通常、ListとIListを習慣的に取得します。ただし、特にそれらを自分で実装する場合は、ICollectionの方が簡単で、Countプロパティもあります。ありがとう!
メンデルト2008年

21
@Shimmy反復して要素を数えます。または、これを行うLinq名前空間からCount()を呼び出します。
メンデルト2009

1
通常の状況では、IEnumerableをIListに置き換えるだけで十分でしょうか?
Teekin、2010

1
@Helgi IEnumerableは遅延評価されるため、IListが使用できないものに使用できます。たとえば、Piのすべての小数を列挙するIEnumerableを返す関数を作成できます。あなたが完全な結果を予見しようとしない限り、それはうまくいくはずです。Piを含むIListを作成することはできません。しかし、それはすべてかなり学術的なことです。ほとんどの通常の使用について、私は完全に同意します。Countが必要な場合は、IListが必要です。:-)
Mendelt 2010

215

上の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プロパティを持つにキャストしようとし、可能であればそれを使用します。それ以外の場合は繰り返します。

したがって、可能な限り最高のパフォーマンスが得られるので、オブジェクトに対してCount()拡張メソッドを使用するのIEnumerable<T>が最善の策です。


13
ICollection<T>最初にキャストしてみるのがとても面白い。
オスカーメデロス

1
@OscarMederos Enumerableの拡張メソッドのほとんどは、さまざまなタイプのシーケンスを最適化しており、可能な場合はより安価な方法を使用します。
Shibumi

1
上記の拡張機能は.Net 3.5以降で利用可能で、MSDNに文書化されています
クリスチャン

私はあなたがジェネリック型Tを必要としないと思いますIEnumerator<T> enumerator = source.GetEnumerator()、単にこれを行うことができます:IEnumerator enumerator = source.GetEnumerator()そしてうまくいくはずです!
Jaider

7
@ジェイダー-それはそれよりも少し複雑です。ステートメントが自動的に破棄できるようにするIEnumerable<T>継承。ではない。したがって、どちらの方法で電話をかける場合でも、最後にIDisposableusingIEnumerableGetEnumeratorvar d = e as IDisposable; if (d != null) d.Dispose();
Daniel Earwickerを

87

余分な情報を追加するだけです:

Count()拡張子は常に反復しません。カウントがデータベースに送られるLinq to Sqlを考えますが、すべての行Count()を戻すのではなく、Sql コマンドを発行してその結果を返します。

さらに、コンパイラー(またはランタイム)は、objects Count()メソッドがある場合はそれを呼び出すように十分にスマートです。だから、だではない、完全に無知であることと、常に要素をカウントするために反復し、他のレスポンダが言うように。

linqの遅延評価では、要素があると判断できると短絡する可能性があるため、プログラマーが拡張メソッドif( enumerable.Count != 0 )を使用してチェックしている多くの場合ははるかに効率的です。また、読みやすくなりますAny()if( enumerable.Any() )


2
コレクションと配列に関して。コレクションを使用する場合は、.Countプロパティが常にサイズを知っているので、プロパティを使用してください。クエリを実行collection.Countすると、追加の計算は行われず、単に既知のカウントが返されます。Array.length私の知る限り同じです。ただし、.Any()ソースの列挙子を使用して取得し、using (IEnumerator<TSource> enumerator = source.GetEnumerator())可能であればtrueを返しますenumerator.MoveNext()。コレクションの場合:if(collection.Count > 0)、配列:if(array.length > 0)と列挙可能の場合if(collection.Any())
2013年

1
最初のポイントは厳密には正しくないです... SQLにLINQは使用しています。この拡張メソッドと同じではありませんこれを。2番目を使用する場合、カウントはSQL関数としてではなくメモリ内で実行されます
AlexFoxGill

1
@AlexFoxGillは正しいです。に明示的にキャストIQueryably<T>したIEnumerable<T>場合、SQLカウントは発行されません。これを書いたとき、Linq to Sqlは新しいものでした。Any()列挙型、コレクション、SQLの方が(一般的に)はるかに効率的で読みやすいので、私は使用することを強く求めていたと思います。答えを改善してくれてありがとう。
Robert Paulson、2016年

12

私の友人は、あなたがこれを行うことができない理由を説明する一連のブログ投稿を持っています。彼は、IEnumerableを返す関数を作成します。ここで、各反復は次の素数を返し、にulong.MaxValue至るまで、次の項目は要求されるまで計算されません。簡単でポップな質問:返されるアイテムの数は?

ここに投稿がありますが、長いです:

  1. Beyond Loops(他の投稿で使用される初期EnumerableUtilityクラスを提供します)
  2. イテレートのアプリケーション(初期実装)
  3. クレイジー拡張メソッド:ToLazyList(パフォーマンスの最適化)

2
私は本当に、MSが列挙可能なものに彼ら自身について何ができるかを説明する方法を定義することを本当に望んでいます(「何も知らない」が有効な答えです)。列挙可能なものは、「有限であることを知っていますか?」、「N未満の要素で有限であることを知っていますか?」、「無限であることを知っていますか?」 (役に立たない場合)すべてに「いいえ」と答えます。そのような質問をするための標準的な手段があれば、列挙子が無限のシーケンスを返す方がはるかに安全です...
supercat

1
...(そうすることができると言うことができるので)、そしてコードは、無限のシーケンスを返すと主張しない列挙子が制限されている可能性が高いと想定します。このような質問をする手段を含めると(定型文を最小化するために、おそらくプロパティがEnumerableFeaturesオブジェクトを返すようにする)、列挙子は難しいことをする必要はありませんが、そのような質問をすることができます(「あなたは約束できますか?常に同じアイテムのシーケンスを返す」、「基になるコレクションを変更してはならないコードに安全にさらされることはできます」など)は非常に便利です。
スーパーキャット2015

それはかなりクールだと思いますが、今度はそれがイテレータブロックとどれだけうまくかみ合うかを確信しています。なんらかの特別な「収量オプション」などが必要になります。または、属性を使用してイテレータメソッドを装飾することもできます。
Joel Coehoorn、2015

1
イテレータブロックは、他の特別な宣言がない場合、IEnumeratorまたはMS承認のサクセサ(その存在を知っているGetEnumerator実装によって返される可能性がある)の場合でも、返されたシーケンスについて何も知らないことを単に報告する可能性があります。追加情報をサポートすることでしたが、C#はおそらくset yield optionsそれをサポートするステートメントまたは類似のものを取得します。適切に設計されていればIEnhancedEnumerator、多くの「防御的な」呼び出しToArrayToList呼び出しを排除することで、LINQなどをより使いやすくすることができます。特に...
supercat

...のようなものEnumerable.Concatが、それ自体についてよく知っている大きなコレクションと、知らない小さなコレクションを組み合わせるために使用される場合。
スーパーキャット2015

10

IEnumerableは、反復なしではカウントできません。

「通常の」状況では、List <T>などのIEnumerableまたはIEnumerable <T>を実装するクラスが、List <T> .Countプロパティを返すことによってCountメソッドを実装することが可能です。ただし、Countメソッドは、実際にはIEnumerable <T>またはIEnumerableインターフェイスで定義されたメソッドではありません。(実際には、唯一のGetEnumeratorです。)これは、クラス固有の実装を提供できないことを意味します。

Countは、静的メソッドEnumerableで定義された拡張メソッドです。つまり、そのクラスの実装に関係なく、IEnumerable <T>派生クラスの任意のインスタンスで呼び出すことができます。ただし、これらのクラスの外部の単一の場所に実装されることも意味します。もちろんこれは、これらのクラスの内部から完全に独立した方法で実装する必要があることを意味します。カウントを行う唯一のそのような方法は、反復によるものです。


これは、反復しないと数えられないことの良い点です。カウント機能は、IEnumerableを実装するクラスに関連付けられています...したがって、受信するIEnumerableのタイプを確認する必要があります(キャストによって確認)。List<>とDictionary <>には、カウントして使用する特定の方法があることがわかります。あなたがタイプを知った後にのみそれら。私はこのスレッドが個人的にとても役に立ったので、あなたの返事とクリスに感謝します。
PositiveGuy

1
Danielの回答によると、この回答は厳密には当てはまりません。実装は、オブジェクトが「Count」フィールドを持つ「ICollection」を実装しているかどうかをチェックします。もしそうなら、それを使用します。(それが'08年にそれほど賢かったかどうかはわかりません。)
ToolmakerSteve


8

いいえ、一般的ではありません。enumerableを使用する際の1つのポイントは、列挙内の実際のオブジェクトのセットが(事前に、またはまったく)不明であることです。


あなたが提起した重要なポイントは、そのIEnumerableオブジェクトを取得した場合でも、それをキャストして、それがどのタイプであるかを理解できるかどうかを確認する必要があるということです。私のコードのようにIEnumerableをもっと使用しようとする人にとって、これは非常に重要なポイントです。
PositiveGuy

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
これは、非ジェネリックバリアントIEnumerable(型指定子なし)では機能しません
Marcel

IEnumerableがある場合、.Countの実装はすべての項目を列挙しています。(ICollectionとは異なります)。OPの質問は明らかに「反復なし」です
JDC

5

列挙型を処理している間に進行状況を報告したい場合は、当面の質問(否定で完全に回答されています)を超えて、私のブログ記事「Linqクエリ中の進行状況の報告」をご覧ください。

これを行うことができます:

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

4

私はメソッド内でそのように使用して、渡されたIEnumberableコンテンツをチェックしました

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

}

このようなメソッドの内部:

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

}

1
なぜそうするのですか?「カウント」は高価になる可能性があるため、IEnumerableを指定すると、必要な出力を適切なデフォルトに初期化してから、「iEnum」の反復を開始する方が安上がりです。ループを実行しない空の「iEnum」が有効な結果になるようなデフォルトを選択します。時には、これは、ループが実行されたかどうかを知るためにブールフラグを追加することを意味します。それは不器用ですが、「カウント」に依存することは賢明ではないようです。このフラグが必要な場合、コードは次のようになります bool hasContents = false; if (iEnum != null) foreach (object ob in iEnum) { hasContents = true; ... your code per ob ... }
ToolmakerSteve 2017

...それは初回のみ行われるべき特別なコードを追加するのは簡単でもあり、または唯一の反復上の他の最初の時間より: `... {場合(!hasContents){hasContents =はtrue。..ワンタイムコード..; } else {..code for first time ..} ...} "確かに、これは、ループの前のif内に1回限りのコードが含まれているが、コストが" .Count()」、これが移動するための方法で、問題になることがあります。
ToolmakerSteve

3

.NetのバージョンとIEnumerableオブジェクトの実装によって異なります。Microsoftは、実装を確認するためにIEnumerable.Countメソッドを修正し、ICollection.CountまたはICollection <TSource> .Countを使用しています。詳細については、https: //connect.microsoft.com/VisualStudio/feedback/details/454130を参照してください

そして以下は、System.Linqが存在するSystem.Core用のIldasmからのMSILです。

.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

2

ここでは、遅延評価遅延実行についての素晴らしい議論があります。基本的には、その値を取得するためにリストを具体化する必要があります。


2

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)です


1

私が見つけた最良の方法は、リストに変換して数えることです。

IEnumerable<T> enumList = ReturnFromSomeFunction();

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

-1

ToListを呼び出すことをお勧めします。はい、早期に列挙を行っていますが、アイテムのリストには引き続きアクセスできます。


-1

最高のパフォーマンスは得られない可能性がありますが、LINQを使用してIEnumerableの要素をカウントできます。

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

結果は単に「 `return Enumerable.Count();」を実行することとどのように異なりますか?
ToolmakerSteve 2017

いい質問ですね。それとも、実際にはこのStackoverflowの質問に対する答えです。
Hugo

-1

これを行う最も簡単な方法だと思います

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

リファレンス:system.linq.enumerable


問題は、「IEnumerable <T>からの項目を反復せずにカウントしますか?」です。これはどのように答えますか?
謎めい

-3

私が使用しIEnum<string>.ToArray<string>().Length、それは正常に動作します。


これは正常に動作するはずです。IEnumerator <Object> .ToArray <Object> .Length
din

1
なぜあなたは、むしろすでに与えられ、より簡潔かつ迅速-を行うソリューションよりも、これを行うのですか3年前のあなたより書かれたダニエルの高upvoted答え "、IEnum<string>.Count();「?
ToolmakerSteve 2017

あなたが正しいです。どういうわけか私はダニエルの答えを見落としました。おそらく彼が実装から引用し、拡張メソッドを実装していると思ったため、コードの少ないソリューションを探していました。
OliverKötter2017

私の悪い答えを処理する最も一般的な方法は何ですか?削除しますか?
OliverKötter2017

-3

文字列のリストがある場合は、このようなコードを使用します。

((IList<string>)Table).Count
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.