8

私は、linq メソッドをチェーンしているときに、特に同じメソッドを複数回チェーンしているときに、C# コンパイラが何をするかについて頭を悩ませようとしています。

簡単な例: 2 つの条件に基づいて一連の int をフィルター処理しようとしているとします。

最も明白なことは、次のようなことです。

IEnumerable<int> Method1(IEnumerable<int> input)
{
    return input.Where(i => i % 3 == 0 && i % 5 == 0);
}

ただし、where メソッドを連鎖させて、それぞれに 1 つの条件を指定することもできます。

IEnumerable<int> Method2(IEnumerable<int> input)
{
    return input.Where(i => i % 3 == 0).Where(i => i % 5 == 0);
}

Reflector の IL を見てみました。2つの方法では明らかに異なりますが、それをさらに分析することは、現時点では私の知識を超えています:)

私は次のことを知りたい:
a)コンパイラが各インスタンスで異なる動作をすることと、その理由。
b)パフォーマンスへの影響はありますか(マイクロ最適化を試みているのではなく、ただ興味があります!)

4

2 に答える 2

11

(a)に対する答えは短いですが、以下で詳しく説明します。

コンパイラは実際には連鎖を行いません - オブジェクトの通常の構成を通じて実行時に行われます! ここには、一見しただけに見える魔法よりもはるかに少ない魔法があります。Jon Skeetは最近、彼のブログ シリーズ「LINQ to Objects の再実装」の「Where 節」のステップを完了しました。それを一読することをお勧めします。

Where非常に簡単に言えば、何が起こるかというと、拡張メソッドを呼び出すたびに、前のもの (呼び出したもの) への参照と、指定したラムダのWhereEnumerable2 つを持つ新しいオブジェクトが返されます。IEnumerableWhere

これを反復し始めるとWhereEnumerable(たとえば、コードの後半で)、参照しforeach内部的に単純に反復し始めます。IEnumerable

「これforeachは私のシーケンスの次の要素を私に求めたので、振り返ってあなたのシーケンスの次の要素を求めています」.

これは、実際には実要素のある種の配列またはストレージである原点に到達するまで、チェーンをずっと下っていきます。次に、各 Enumerable が「OK、ここに私の要素があります」と言って、それをチェーンに戻して、独自のカスタム ロジックも適用します。a のWhere場合、ラムダを適用して、要素が基準を満たしているかどうかを確認します。その場合は、次の呼び出し元に進むことができます。失敗すると、その時点で停止し、参照されている Enumerable に戻り、次の要素を要求します。

これは、全員MoveNextが false を返すまで発生し続けます。これは、列挙が完了し、要素がなくなることを意味します。

(b)に答えるには、常に違いがありますが、ここでは気にするのはあまりにも些細なことです。ご心配なく :)

于 2010-11-12T02:15:07.367 に答える
1
  1. 1 つ目はイテレータを 1 つ使用し、2 つ目は 2 つ使用します。つまり、1 つ目は 1 つのステージでパイプラインをセットアップし、2 つ目は 2 つのステージを含みます。

  2. 2 つの反復子は、1 つよりもわずかにパフォーマンスが劣ります。

于 2010-11-12T02:12:18.910 に答える