Linqと遅延実行のイメージ
Linqでは要素を取得する処理は本当にその要素が必要なってはじめて実行されます。これを遅延実行と呼びます。Linqによる遅延実行は、複数のデバイスが通信によってデータを要求・応答している様子をイメージをすればよいと思います。
デバイス1がデータを発生します。デバイス2がデータを中間処理します。デバイス3がデータを最終処理します。処理はデバイス3がデバイス2に対してデータを要求することで開始します。デバイス2は要求を受けてデバイス1に対してデータを要求します。デバイス1はデバイス2に対して応答を返します。デバイス2は応答を受けたらデバイス3に応答を返します。これが繰り返されるのです。
デバイス1やデバイス2は、図中自分より右のデバイスにデータを要求されて初めて応答します。遅延実行はこのような仕組みで働きます。
遅延実行で省メモリ
1 | using System; |
リスト1の例はGeneratorが0から10000までの整数を発生させます。WhereでGeneratorから渡された要素が2より大きいものだけを通すようにフィルタリングします。Each内で結果を表示します。遅延実行の様子はシーケンス図で確認してください。MoveNext Current
がデータの要求、yield return
が応答です。
もしGeneratorのすべての処理が終わってからWhereの処理に移るならば、int約10,000個分のバッファが必要になります。しかし遅延実行を行っているために1個分のバッファで済むのです。この場合遅延実行のメリットは省メモリです。
遅延実行で無限Generator
1 | using System; |
リスト2ではGeneratorが無限に整数を発生させています。TakeWhileでGeneratorから渡された要素が3より小さい間だけ通すようにフィルタリングします。無限ループにならずに終了するのは0,1,2,3だけが処理されるからです。無限ループにならずに実行できるのは遅延実行のおかげです。
TakeWhileをWhereに変えると遅延実行はされるものの処理は続き無限ループになります。無限に要素を返すジェネレータはTakeWhileのように処理を打ち切るフィルタと組み合わせて使う必要があります。
遅延実行されない場合
Linqがいつも遅延実行されるわけではありません。Max,Min,OrderByなどは原理的にすべての要素が揃わないと最初の要素すら決まりませんのでこのようなクエリ演算子は遅延実行されません(マイクロソフトでは集中評価と呼んでいるようです)。
Linqで言う遅延実行はメソッド呼び出し時の引数の遅延評価とは意味が異なります。Linqに限らず引数にデリゲート・ラムダ式を指定して評価を呼び出し後に行うことを遅延評価と呼んでいます。遅延実行のためには遅延評価も必要です。