web-dev-qa-db-ja.com

繰り返すことなくIEnumerable <T>から項目を数えますか?

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

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

メインの反復の前に反復することなくmの値を見つけることができる方法はありますか?

私が自分自身を明確にしたことを願っています。

292
sebagomez

IEnumerableはこれをサポートしていません。これは仕様によるものです。 IEnumerableはあなたがそれらを必要とする直前にあなたが求める要素を得るために遅延評価を使います。

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

308
Mendelt

System.Linq.Enumerable.CountIEnumerable<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;

そのため、Countプロパティを持つICollection<T>にキャストしようとし、可能であればそれを使用します。それ以外の場合は繰り返します。

だからあなたの最善の策はIEnumerable<T>オブジェクトでCount()拡張メソッドを使うことです。

198

追加の情報を追加するだけです。

Count()エクステンションは必ずしも繰り返すとは限りません。 Linq to Sqlでは、カウントはデータベースに送られますが、すべての行を戻すのではなく、Sql Count()コマンドを発行してその結果を返します。

さらに、コンパイラ(またはランタイム)は、Count()メソッドがあればそれを呼び出すことができるほど賢いです。他の回答者が言うように、それはではなく、完全に無知で、常に要素を数えるために繰り返します。

if( enumerable.Count != 0 )のように、プログラマが単にAny()拡張メソッドを使用してif( enumerable.Any() )をチェックしている多くの場合、要素があると判断できればショートできるので、linqの遅延評価でははるかに効率的です。もっと読みやすいです

84
Robert Paulson

私の友人はなぜあなたがこれをすることができないかの実例を提供する一連のブログ投稿をしています。彼はIEnumerableを返す関数を作成します。ここで各繰り返しはulong.MaxValueまでの次の素数を返し、次の項目はそれが求められるまで計算されません。簡単な、ポップな質問:いくつの商品が返されますか?

ここに投稿がありますが、それらはちょっと長いです:

  1. ループを超えて (他の記事で使用されている最初のEnumerableUtilityクラスを提供します)
  2. 繰り返し適用 (初期実装)
  3. クレイジーエクステンションメソッド:ToLazyList (パフォーマンスの最適化)
11
Joel Coehoorn

IEnumerableは繰り返しなしでは数えられません。

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

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

10
Chris Ammerman

あるいは、次のようにすることもできます。

Tables.ToList<string>().Count;
9
h_alsharaf

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

8
JesperE

あなたは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'を得るでしょう。

8
prosseek

私のブログ投稿 を処理しながら進捗状況を報告することを検討しているのであれば、あなたの直近の質問(否定的に完全に答えられた)を超えて進みます。 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
      };
5
Samuel Jack

メソッド内でこのような方法で渡したIEnumberableの内容をチェックしました

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

}

このようなメソッドの中に:

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

}
4
Shahidul Haque

それは.NetのどのバージョンとあなたのIEnumerableオブジェクトの実装に依存します。マイクロソフトはIEnumerable.Countメソッドで実装をチェックするように修正し、ICollection.CountまたはICollection <TSource> .Countを使用します。詳細はこちらを参照してください https://connect.Microsoft.com/ VisualStudio/feedback/details/454130

そして以下はSystem.CoreのためのIldasmからの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
3
prabug

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

2
Roman Golubin

これは 遅延評価遅延実行 についての素晴らしい議論です。基本的にあなたはその値を得るためにリストを具体化しなければなりません。

2
JP Alioto

高速カウントを行う唯一の方法は、元のコレクションにインデクサ(配列など)がある場合です。最低限の要件でジェネリックコードを作成するためにはIEnumerableを使うことができますが、もしあなたがカウントを必要とするなら私の好ましい方法はこのインターフェースを使うことです:


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

元のコレクションにインデクサーが含まれていない場合、Count実装は、パフォーマンスO(n)で既知のヒット率でコレクションに対して反復できます。

IEnumAndCountのようなものを使用したくない場合は、Daniel Earwickerがこの質問の冒頭で述べている理由からLinq.Countを使用するのが最善の策です。

がんばろう !

1
Eric Ouellet

ToListを呼び出すことをお勧めします。はい、あなたは早く列挙をしています、しかしあなたはまだあなたのアイテムのリストにアクセスすることができます。

0
Jonathan Allen

いいえ.

あなたはあなたが書いたコードのどこかでその情報が利用できると思いますか?

あなたは、コンパイラが2つしかないことを「見る」ことができると主張するかもしれません、しかしそれはそれがその特定の病理学的ケースだけを探してすべてのイテレータメソッドを分析する必要があることを意味します。たとえそれがあったとしても、IEnumerableの限界を考えれば、どのようにそれを読むのでしょうか?

0
James Curran

最高のパフォーマンスが得られない可能性がありますが、IEnumerableの要素を数えるためにLINQを使用することができます。

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