web-dev-qa-db-ja.com

LinqのToListメソッド

私が間違っていない場合、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);
}

従来の方法はループが1回だけなので、より高速だと思います。上記のように、LinqはWhereメソッドとToList()メソッドの2回繰り返します。

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

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

13
user1976469

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

それはあなたのコードに依存します。ほとんどの場合、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()は値を取得し、それをリストに追加します。

これが意味するのは、元のコレクションのすべてのnアイテムが条件に一致する場合、MoveNext()は2と呼ばれるということです。n 回数、nWhere()からの回数およびnToList()からの回数。

11
svick
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 +1回でN回ではないのかと尋ねる前に。それは、彼が最後に要素がなくなったときにfalseを返すためです。要素数+リストの終わりにします。

5
Evelie

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

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

1
Jim Wooley

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

とは言うものの、WhereSelectOrderByおよびその他のLINQIEnumerable拡張メソッドは、一般に、可能な限り遅延して実装されます( yield キーワードがよく使用されます)。つまり、必要がない限り、データを処理しません。あなたの例から:

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

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

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

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

1
SWeko