2016-04-26 4 views
1

私は最近、.NETのLINQ実装で作成されたオブジェクトが、特定の列挙型に対して非効率的であることを知りました。C#:IEnumerable <T> .Select()は効率的でない場合がありますか?

は、このコードを見てみましょう:

public class DummyCollection : ICollection<int> 
{ 
     public IEnumerator<int> GetEnumerator() 
     { 
      throw new Exception(); 
     } 
     public int Count 
     { 
      get 
      { 
       return 10; 
      } 
     } 
    //some more interface methods 
} 

は基本的に、DummyCollectionのインスタンスは、10の大きさを持っているが、それが実際に列挙されている場合は例外をスローします。

今ここで:

var d = new DummyCollection(); 
Console.WriteLine(d.Count()); 

10がエラーなしで印刷が、コードのこの部分である:

var l = d.Select(a=> a); 
Console.WriteLine(l.Count()); 

は、Lのサイズが10となると言うことが自明であるにもかかわらず、例外をスロー(Selectは1対1マッピングを提供するので)。これは、基本的には、Ienumerableの長さをチェックするとき、入力がSelect-wrapped Collectionであり、計算時間をO(1)から驚異的なO(n)に延長することができます選択機能は特に煩雑である)。

LINQのジェネリックスを求めるときに効率を犠牲にすることがわかっていますが、これは修正するのが簡単な問題のようです。私はオンラインでチェックし、誰かがこれに対処できなかった。この欠点を回避する方法はありますか?誰もこれを調べているのですか?誰でもこれを修正していますか?これはそれほど大きな問題ではない、ちょっとしたケースですか?どんな洞察にも感謝します。

+1

とにかく、 'GetEnumerator'で例外を投げて、どんな場合でも' Count() 'を呼び出すことができるのは本当ですか? –

+2

@MatíasFidemraizer例外スローは、可能な限り列挙を避けるための単なるプレースホルダです。これは、非常に長いメソッド呼び出し、または非常に大きなコレクション、または列挙時に変更されるコレクション(非同期環境では危険なオブジェクトのロックなど)に置き換えることができます。 – bentheiii

+1

'OrderBy'にも同じ問題があります。 'OrderBy'はカウントを変更しませんが、' Count() 'を呼び出すとまだ完全なソートが実行されます。あなたはいくつかの反射shenaningansを使用してソートせずに正しいカウントを得ることができます。 http://stackoverflow.com/questions/17493076/count-an-iorderedenumerered-umer-without-consuming-it – HugoRune

答えて

6

Count()拡張メソッドの実装方法は、hereです。基本的にはこのようなものです:あなたがメソッドのチェックが最初sourceで見ることができるように

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

だけ返し、そのような場合には、その後の要素を数える反復処理する必要がない、タイプICollection<TSource>またはICollectionであるCountプロパティ。

最初のケースでは10を返すというプロパティが呼び出され、GetEnumerator()メソッドは呼び出されません。

Select()メソッドを使用すると、ICollection(上記のリンクではSelect()の実装も参照できます)ではない別のタイプにコレクションをラッピングするので、繰り返しが必要です。

2番目のケースでは、Count()に電話するとGetEnumerator()メソッドが呼び出され、例外がスローされます。

+0

私はCount()メソッドへの使用を最適化するために、ICollection 型オブジェクト(ソースがICollection もある場合)を返すようにSelectメソッドを改良することができると言っています。 – bentheiii

+0

@bentheiii:それは本当だ。おそらく遅延実行を維持するために実装されていませんでした。 –

+0

@bentheiii:LINQはすべて怠け者です。 Selectは、一連のものを返しません。それは何かを返します、あなたがそれを繰り返すとき***は鎖を通って一度に項目を引っ張ります。意味は、あなたが「選択」するときに記憶装置がないということです。結果をコレクションに格納することは、LINQの使用目的には悲惨です。アイテムは、(格納された)コレクションを生成する操作ではなく、一度に1つずつ(非常に近視的な方法で)処理されるパイプラインのように考えてください。 – spender

2

IEnumerable<T>の概念はCountではありません。これは、実装の中に存在します。(ここでは奇妙なショートカットを除いて)LINQ to Objectsでは役割がありません。実装がIEnumerable<T>ICollection<T>など)の場合、Selectと投影すると、出力は ... Countになります。

LINQは、現在のアイテムと次のアイテム(またはシーケンスの終わり)の概念のみで、アイテムのシーケンスを一度に1つずつ扱うものと考えるべきです。アイテムの数を知ることは、いくつかの最適化されたケースを除いて、カウントされるすべてのアイテムの反復を必要とする(潜在的に)高価な操作です。

は、LINQ は、インデックスおよびカウントに優先して、反復のは、あなたがしようエラー IEnumerableは、飛ぶようにいくつかのスーパーの奇妙な特別なケースを必要としている反復することを意味依存していることを考えます。私にとって、これは非常に便利なユースケースではありません。

関連する問題