LINQ であり、IEnumerable<T>
プルベースです。これは、一般的に LINQ ステートメントの一部である述語とアクションは、値が取得されるまで実行されないことを意味します。さらに、述語とアクションは、値が取得されるたびに実行されます (たとえば、秘密のキャッシュは行われません)。
からのプルは、値をプルするために呼び出しと繰り返し呼び出しによって列挙子を取得するための実際の構文糖衣でIEnumerable<T>
あるステートメントによって行われます。foreach
IEnumerable<T>.GetEnumerator()
IEnumerator<T>.MoveNext()
ToList()
、ToArray()
、などのLINQ 演算子はステートメントToDictionary()
をToLookup()
ラップするため、これらのメソッドはプルを実行します。、 、などforeach
の演算子についても同じことが言えます。これらのメソッドには、ステートメントを実行して作成する必要がある単一の結果を生成するという共通点があります。Aggregate()
Count()
First()
foreach
多くの LINQ 演算子は、新しいIEnumerable<T>
シーケンスを生成します。結果のシーケンスから要素がプルされる場合、オペレーターはソース シーケンスから 1 つ以上の要素をプルします。演算子は最もSelect()
明白な例ですが、他の例としてはSelectMany()
、、、、、Where()
およびがあります。これらのオペレーターはキャッシュを行いません。N 番目の要素が aから取得されると、ソース シーケンスから N 番目の要素が取得され、指定されたアクションを使用して射影が適用され、それが返されます。ここでは何も秘密はありません。Concat()
Union()
Distinct()
Skip()
Take()
Select()
他の LINQ 演算子も新しいIEnumerable<T>
シーケンスを生成しますが、それらは実際にソース シーケンス全体をプルし、ジョブを実行してから新しいシーケンスを生成することによって実装されます。これらの方法にはReverse()
、 、OrderBy()
およびが含まれGroupBy()
ます。ただし、オペレーターによって行われるプルは、オペレーター自体がプルされたときにのみ実行されます。つまり、foreach
何かを実行する前に、LINQ ステートメントの「最後」にループが必要です。これらのオペレーターは、ソース シーケンス全体をすぐにプルするため、キャッシュを使用していると主張できます。ただし、このキャッシュはオペレーターが繰り返されるたびに構築されるため、実際には実装の詳細であり、同じOrderBy()
操作を同じシーケンスに複数回適用していることを魔法のように検出するものではありません。
あなたの例でToList()
は、プルを行います。外側のアクションはSelect
100 回実行されます。このアクションが実行されるたびAggregate()
に、XML 属性を解析する別のプルが実行されます。合計で、コードはInt32.Parse()
200 回呼び出されます。
これを改善するには、反復ごとに属性を取得するのではなく、一度属性を取得します。
var X = XElement.Parse (@"
<ROOT>
<MUL v='2' />
<MUL v='3' />
</ROOT>
")
.Elements ()
.Select (t => Int32.Parse (t.Attribute ("v").Value))
.ToList ();
Enumerable.Range (1, 100)
.Select (s => x.Aggregate (s, (t, u) => t * u))
.ToList ()
.ForEach (s => Console.WriteLine (s));
今Int32.Parse()
は2回しか呼び出されません。ただし、コストは、属性値のリストを割り当てて保存し、最終的にガベージ コレクションを行わなければならないことです。(リストに 2 つの要素が含まれている場合は、大きな問題ではありません。)
属性をプルする最初のものを忘れた場合ToList()
でも、コードは実行されますが、元のコードとまったく同じパフォーマンス特性であることに注意してください。属性を格納するためにスペースは使用されませんが、反復ごとに解析されます。