linqは表面に表示されるよりも効率的ですか?


13

このようなものを書くと:

var things = mythings
    .Where(x => x.IsSomeValue)
    .Where(y => y.IsSomeOtherValue)

これは次と同じですか:

var results1 = new List<Thing>();
foreach(var t in mythings)
    if(t.IsSomeValue)
        results1.Add(t);

var results2 = new List<Thing>();
foreach(var t in results1)
    if(t.IsSomeOtherValue)
        results2.Add(t);

または、以下のように機能するいくつかの魔法があります:

var results = new List<Thing>();
foreach(var t in mythings)
    if(t.IsSomeValue && t.IsSomeOtherValue)
        results.Add(t);

それともまったく異なるものですか?


4
これはILSpyで表示できます。
ChaosPandion

1
ILSpyはあなたの友達であるという2番目のChaosPandionの答えよりも、2番目の例に似ています。
マイケル

回答:


27

LINQクエリは遅延しています。それはコードを意味します:

var things = mythings
    .Where(x => x.IsSomeValue)
    .Where(y => y.IsSomeOtherValue);

ほとんど何もしません。元の列挙は、(mythings得られた列挙は(時)のみ列挙されているthings)によって、例えば、消費されforeach、ループ.ToList()、又は.ToArray()

を呼び出すとthings.ToList()、後者のコードとほぼ同等になりますが、おそらく列挙子からの(通常はわずかな)オーバーヘッドがあります。

同様に、foreachループを使用する場合:

foreach (var t in things)
    DoSomething(t);

パフォーマンスは次のとおりです。

foreach (var t in mythings)
    if (t.IsSomeValue && t.IsSomeOtherValue)
        DoSomething(t);

すべての結果を計算してリストに保存するのとは対照的に、列挙型の遅延アプローチのパフォーマンスの利点のいくつかは、メモリをほとんど使用しない(一度に1つの結果しか保存されないため) -フロントコスト。

enumerableが部分的にのみ列挙されている場合、これは特に重要です。次のコードを検討してください。

things.First();

LINQの実装方法はmythings、where条件に一致する最初の要素までのみ列挙されます。その要素がリストの早い段階にある場合、これによりパフォーマンスが大幅に向上します(例:O(n)ではなくO(1))。


1
LINQとそれを使用する同等のコードのパフォーマンスの違いの1つforeachは、LINQがオーバーヘッドを伴うデリゲート呼び出しを使用することです。これは、条件が非常に高速に実行される場合に重要になることがあります(頻繁に実行されます)。
svick

2
それが列挙子のオーバーヘッドの意味です。それはいくつかの(まれな)場合に問題になる可能性がありますが、私の経験ではそれほど頻繁ではありません-通常、最初にかかる時間は非常に短いか、あなたがしている他の操作よりもはるかに重要です。
シアンフィッシュ

Linqの遅延評価の厄介な制限は、ToListまたはのようなメソッドを経由する場合を除き、列挙の「スナップショット」を取得する方法がないことToArrayです。そのようなものがに適切に組み込まれていれば、IEnumerableすべてを生成することなく、将来変化する可能性のある側面を「スナップショット」するようにリストに要求することが可能でした。
スーパーキャット

7

次のコード:

var things = mythings
    .Where(x => x.IsSomeValue)
    .Where(y => y.IsSomeOtherValue);

怠equivalentな評価のため、何も起こりません。

var things = mythings
    .Where(x => x.IsSomeValue)
    .Where(y => y.IsSomeOtherValue)
    .ToList();

評価が開始されるため、異なります。

の各アイテムはmythings最初に与えられWhereます。合格した場合、2番目に渡されWhereます。合格すると、出力の一部になります。

そのため、これは次のようになります。

var results = new List<Thing>();
foreach(var t in mythings)
{
    if(t.IsSomeValue)
    {
        if(t.IsSomeOtherValue)
        {
            results.Add(t);
        }
    }
}

7

実行の遅延は別として(他の回答では既に説明していますが、別の詳細を指摘しておきます)、2番目の例のようになります。

ちょうどあなたが呼んで想像してみましょうToListthings

の実装Enumerable.Whereはa を返しますEnumerable.WhereListIterator。それを呼び出すWhereWhereListIterator(別名Where-calls)、を呼び出すことはなくなりますEnumerable.WhereEnumerable.WhereListIterator.Where、実際には(を使用してEnumerable.CombinePredicates)述語を結合します。

のようになりif(t.IsSomeValue && t.IsSomeOtherValue)ます。


「Enumerable.WhereListIteratorを返します」とクリックしてくれました。おそらく非常にシンプルなコンセプトですが、それがILSpyで見落としていたものです。ありがとう
ConditionRacer

より詳細な分析に関心がある場合は、Jon Skeetによるこの最適化の再実装を参照してください。
セルビー

1

いいえ、同じではありません。あなたの例でthingsIEnumerableであり、この時点ではまだ実際の配列やリストではなくイテレータにすぎません。さらに、thingsは使用されていないため、ループは評価されません。このタイプでIEnumerableyield、Linq命令によって処理された要素を反復処理し、さらに多くの命令でさらに処理することができます。つまり、実際にはループは1つだけです。

しかし、.ToArray()またはのような命令を追加するとすぐ.ToList()に、実際のデータ構造の作成を命令しているため、チェーンに境界が設定されます。

この関連するSOの質問を参照してください:https : //stackoverflow.com/questions/2789389/how-do-i-implement-ienumerable

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.