2011-08-16 18 views
1

iは、に使用されるデフォルトのイテレータは拡張メソッドがとを選択していることに気づきました。私は適切にコードを読めば、このようなカウント()ToListメソッドなど他のいくつかの拡張メソッドを、(原因)遅く実行します。WhereSelectArrayIteratorがICollectionを実装していないのはなぜですか? <strong>WhereSelectArrayIterator</strong> - - <strong>いるICollection</strong>インタフェースを実装していない反射板を通じて<strong>System.Linq.Enumerable</strong>を見て

public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector) 
{ 
    // code above snipped 
    if (source is List<TSource>) 
    { 
     return new WhereSelectListIterator<TSource, TResult>((List<TSource>) source, null, selector); 
    } 
    // code below snipped 
} 

private class WhereSelectListIterator<TSource, TResult> : Enumerable.Iterator<TResult> 
{ 
    // Fields 
    private List<TSource> source; // class has access to List source so can implement ICollection 
    // code below snipped 
} 


public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection, IEnumerable 
{ 
public List(IEnumerable<T> collection) 
{ 
    ICollection<T> is2 = collection as ICollection<T>; 
    if (is2 != null) 
    { 
     int count = is2.Count; 
     this._items = new T[count]; 
     is2.CopyTo(this._items, 0); // FAST 
     this._size = count; 
    } 
    else 
    { 
     this._size = 0; 
     this._items = new T[4]; 
     using (IEnumerator<T> enumerator = collection.GetEnumerator()) 
     { 
      while (enumerator.MoveNext()) 
      { 
       this.Add(enumerator.Current); // SLOW, CAUSES ARRAY EXPANSION 
      } 
     } 
    } 
} 

}

を私は疑いを確認した結果でこれをテストしてみました:

いるICollection:2388.5222ミリ

IEnumerabl E:3308.3382ミリ

ここでは、テストコードです

// prepare source 
    var n = 10000; 
    var source = new List<int>(n); 
    for (int i = 0; i < n; i++) source.Add(i); 

    // Test List creation using ICollection 
    var startTime = DateTime.Now; 
    for (int i = 0; i < n; i++) 
    { 
     foreach(int l in source.Select(k => k)); // itterate to make comparison fair 
     new List<int>(source); 
    } 
    var finishTime = DateTime.Now; 
    Response.Write("ICollection: " + (finishTime - startTime).TotalMilliseconds + " ms <br />"); 

    // Test List creation using IEnumerable 
    startTime = DateTime.Now; 
    for (int i = 0; i < n; i++) new List<int>(source.Select(k => k)); 
    finishTime = DateTime.Now; 
    Response.Write("IEnumerable: " + (finishTime - startTime).TotalMilliseconds + " ms"); 

は、私は何かが足りないか、これは、フレームワークの将来のバージョンで修正される予定は?

ありがとうございました。

答えて

5

LINQ to Objectsは、特定の操作を最適化するためにいくつかのトリックを使用します。たとえば、2つの.Whereステートメントを連鎖させると、述語は1つのWhereArrayIteratorに結合され、前のステートメントはガベージコレクションされます。同様に、Whereとそれに続くSelectは、WhereArrayiteratorがガベージコレクションされるように、結合された述語を引数として渡してWhereSelectArrayIteratorを作成します。したがって、WhereSelectArrayIteratorは、selectorだけでなく、predicateの組み合わせに基づいている場合としない場合があります。

sourceフィールドは、与えられた最初のリストのみを記録します。述語のために、反復結果は常にsourceと同じ数の項目を持つとは限りません。 LINQは遅延評価を目的としているため、.Count()が呼び出されると時間を節約できるように、sourcepredicateと評価するべきではありません。これにより、手動で.ToList()を呼び出すのと同じくらいパフォーマンスが低下し、複数のWhereSelect句でユーザーが実行した場合、複数のリストが不必要に作成されることになります。

Selectが配列で直接呼び出されたときに使用するSelectArrayIteratorを作成するために、LINQ to Objectsをリファクタリングできますか?確かに。パフォーマンスを向上させるでしょうか?若干。どのような費用で?コードの再利用が少ないということは、前進を維持しテストするための追加コードを意味します。

したがって、「なぜ言語/プラットフォームXには機能Yがありますか」という大多数の要点に到達します。すべての機能と最適化にはコストがかかりますが、Microsoftには無制限リソース。他のすべての企業と同じように、彼らはSelectを配列上で実行し、次に.ToList()を呼び出すコードが実行される頻度を決定するための判断を呼び、その実行を少し速くすることは、 LINQパッケージ

+0

ありがとうございます - それはあらかじめサイズを知ることを妨げる厄介な述語です。私は、プロセスリストのサイズを変更せずにプロセスリストを頻繁に実行し、おそらくwhereSelectIterratorがSelectIterratorのサブクラスであり、icollectionを実装できるケースを作成します。とにかく、もう一度、ありがとう。 –

+0

実際には、スクラッチ、逆:WhereSelectIterratorは親クラスであり、SelectIterratorはICollectionを実装するサブクラスでなければなりません –

関連する問題