14

私が間違っていなければ、ToList() メソッドは、提供されたコレクションの各要素を反復処理し、それらを List の新しいインスタンスに追加して、このインスタンスを返します。

//using linq
list = Students.Where(s => s.Name == "ABC").ToList();

//traditional way
foreach (var student in Students)
{
  if (student.Name == "ABC")
    list.Add(student);
}

上記のように、Linq は Where メソッドと ToList() メソッドに対して 2 回繰り返します。

私が現在取り組んでいるプロジェクトでは、リストが広範囲に使用されており、リスト変数をIEnumerableとして取得し、.ToList を削除すると、上記のように改善できる ToList() やその他のメソッドの使用がたくさんあることがわかります。 () IEnumerable としてさらに使用します。

これらはパフォーマンスに影響を与えますか?

4

4 に答える 4

12

これらはパフォーマンスに影響を与えますか?

それはあなたのコードに依存します。ほとんどの場合、LINQ を使用すると、わずかなパフォーマンス ヒットが発生します。場合によっては、このヒットが重大になる可能性がありますが、LINQ が遅すぎることがわかっている場合にのみ LINQ を避ける必要があります (つまり、コードのプロファイリングで、コードが遅い理由が LINQ であることがわかった場合)。

しかし、ToList()あまりにも頻繁に使用すると、重大なパフォーマンスの問題が発生する可能性があることは間違いありません。ToList()必要なときだけ電話するべきです。追加することでToList()パフォーマンスが大幅に向上する場合もあります (コレクションが反復されるたびにデータベースからロードされる場合など)。

反復回数に関しては、「2回反復する」とは正確に何を意味するかによって異なります。MoveNext()あるコレクションで呼び出された回数を数えると、はい、Where()この方法を使用すると 2 回反復することになります。一連の操作は次のようになります (簡単にするために、すべての項目が条件に一致すると仮定します)。

  1. Where()が呼び出され、今のところ反復はなくWhere()、特別な列挙型を返します。
  2. ToList()MoveNext()から返された列挙型を呼び出して呼び出されWhere()ます。
  3. Where()MoveNext()元のコレクションを呼び出して、値を取得します。
  4. Where()を返す述語を呼び出しますtrue
  5. MoveNext()ToList()戻り値から呼び出されToList()、値を取得してリストに追加します。
  6. …</li>

これが意味することは、元のコレクション内のn 個MoveNext()の項目がすべて条件に一致する場合、が 2 n回、n回からWhere()、およびn回から呼び出されるということToList()です。

于 2013-02-22T15:31:04.863 に答える
5
var list = Students.Where(s=>s.Name == "ABC"); 

これはクエリを作成するだけで、クエリが使用されるまで要素をループしません。ToList() を呼び出すと、最初にクエリが実行され、要素が 1 回だけループされます。

List<Student> studentList = new List<Student>();
var list = Students.Where(s=>s.Name == "ABC");
foreach(Student s in list)
{
    studentList.add(s);
}

この例も 1 回だけ繰り返します。一度しか使っていないからです。リストは、呼び出されるたびにすべての生徒を反復することに注意してください。名前が ABC の生徒だけではありません。そのクエリ以来。

そして、後の議論のために、私はテスト例を作りました。おそらくこれは IEnumable の最適な実装ではありませんが、本来の機能を果たします。

まず、リストがあります

public class TestList<T> : IEnumerable<T>
{
    private TestEnumerator<T> _Enumerator;

    public TestList()
    {
        _Enumerator = new TestEnumerator<T>();
    }

    public IEnumerator<T> GetEnumerator()
    {
        return _Enumerator;
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        throw new NotImplementedException();
    }

    internal void Add(T p)
    {
        _Enumerator.Add(p);
    }
}

そして、MoveNext が呼び出された回数をカウントしたいので、カスタム列挙子 aswel を実装する必要があります。MoveNext で、プログラムに静的なカウンターがあることを確認します。

public class TestEnumerator : IEnumerator { public Item FirstItem = null; public Item CurrentItem = null;

    public TestEnumerator()
    {
    }

    public T Current
    {
        get { return CurrentItem.Value; }
    }

    public void Dispose()
    {

    }

    object System.Collections.IEnumerator.Current
    {
        get { throw new NotImplementedException(); }
    }

    public bool MoveNext()
    {
        Program.Counter++;
        if (CurrentItem == null)
        {
            CurrentItem = FirstItem;
            return true;
        }
        if (CurrentItem != null && CurrentItem.NextItem != null)
        {
            CurrentItem = CurrentItem.NextItem;
            return true;
        }
        return false;
    }

    public void Reset()
    {
        CurrentItem = null;
    }

    internal void Add(T p)
    {
        if (FirstItem == null)
        {
            FirstItem = new Item<T>(p);
            return;
        }
        Item<T> lastItem = FirstItem;
        while (lastItem.NextItem != null)
        {
            lastItem = lastItem.NextItem;
        }
        lastItem.NextItem = new Item<T>(p);
    }
}

そして、値をラップするカスタムアイテムがあります

public class Item<T>
{
    public Item(T item)
    {
        Value = item;
    }

    public T Value;

    public Item<T> NextItem;
}

実際のコードを使用するには、3 つのエントリを持つ「リスト」を作成します。

    public static int Counter = 0;
    static void Main(string[] args)
    {
        TestList<int> list = new TestList<int>();
        list.Add(1);
        list.Add(2);
        list.Add(3);

        var v = list.Where(c => c == 2).ToList(); //will use movenext 4 times
        var v = list.Where(c => true).ToList();   //will also use movenext 4 times


        List<int> tmpList = new List<int>(); //And the loop in OP question
        foreach(var i in list)
        {
            tmpList.Add(i);
        }                                    //Also 4 times.
    }

そして結論?パフォーマンスにどのように影響しますか?この場合、MoveNext は n+1 回呼び出されます。アイテムの数に関係なく。また、WhereClause は問題ではなく、MoveNext を 4 回実行します。常に最初のリストでクエリを実行するためです。唯一のパフォーマンス ヒットは、実際の LINQ フレームワークとその呼び出しです。作成される実際のループは同じになります。

そして、なぜ N 回ではなく N+1 回なのかを誰かが尋ねる前に。これは、要素がなくなったときに最後に false を返すためです。要素数+リストの終わりにします。

于 2013-02-22T15:24:44.700 に答える
2

これに完全に答えるには、実装に依存します。LINQ to SQL/EF について話している場合、この場合、.ToList が呼び出されたときに反復が 1 回だけ行われ、内部で .GetEnumerator が呼び出されます。次に、クエリ式が TSQL に解析され、データベースに渡されます。結果の行は (1 回) 繰り返され、リストに追加されます。

LINQ to Objects の場合、データを通過するパスも 1 つだけです。where 句で yield return を使用すると、プロセスが反復のどこにあるかを追跡するステート マシンが内部的に設定されます。Where は、完全な反復を実行して一時リストを作成し、それらの結果を残りのクエリに渡しません。アイテムが基準を満たしているかどうかを判断し、一致するものだけを渡します。

于 2013-02-22T16:42:02.453 に答える
1

まず第一に、なぜあなたは私に尋ねるのですか?自分で測定して見てください。

とはいえ、WhereSelectOrderByおよびその他の LINQIEnumerable拡張メソッドは、一般に、可能な限り遅延して実装されます (yieldキーワードは頻繁に使用されます)。つまり、必要な場合を除き、データを操作しないということです。あなたの例から:

var list = Students.Where(s => s.Name == "ABC");

何も実行しません。これはStudents、1000 万個のオブジェクトのリストであっても、一時的に返されます。結果が実際にどこかで要求されるまで、述語はまったく呼び出されません。それが実際に行われることですToList()。「はい、結果-それらすべて-はすぐに必要です」と言います。

ただし、LINQ メソッドの呼び出しには初期オーバーヘッドがあるため、一般的には従来の方法の方が高速ですが、LINQ メソッドの構成可能性と使いやすさ、IMHO はそれを補って余りあるものです。

これらのメソッドがどのように実装されているかを確認したい場合は、Microsoft Reference Sourcesから参照できます。

于 2013-02-22T15:35:24.043 に答える