参考
この記事は、以下の動画を参考にしています。
詳しくは、動画をご覧ください。
遅延実行(Deferred Execution)
LINQが用意する、コレクションを戻すメソッドの多くは、
- メソッドを呼んだ時点では、結果のコレクションの中の個々の要素は生成されていない
- 結果のコレクションから要素を取り出す時点で、その要素が生成される
という動作をします。(遅延実行)
int[] source = [1, 2, 3];
var results = source.Select(x => Guid.NewGuid()); // ここで全要素が生成されるのではない
foreach (var item in results) // 要素が取り出される時点で、Guid.NewGuid()が実行される
{
Console.WriteLine(item);
}
この遅延実行は、問題となる可能性があります。
要素を複数回取り出すと、値が異なる
LINQのメソッドが戻す結果のコレクションから、要素を複数回取り出す場合(例えば、結果のコレクションに対してforeach
を2回使う)、そのたびに要素が生成されてしまいます。
int[] source = [1, 2, 3];
var results = source.Select(x => Guid.NewGuid()); // ここで全要素が生成されるのではない
foreach (var item in results) // ここで実行したGuid.NewGuid()と
{
Console.WriteLine(item);
}
foreach (var item in results) // ここで実行したGuid.NewGuid()は、異なる値を戻す
{
Console.WriteLine(item);
}
上記の例では、同じresults
から2回要素を取り出していますが、取り出した値は異なります。
results
から要素が取り出されるたびに、Select
メソッドに渡したGuid.NewGuid
メソッドが実行されるからです。
要素を取り出そうとしたときに、例外が起きる
LINQのメソッドを呼んだ時点では利用できていたリソースが、結果のコレクションから要素を取り出す時点では利用できなくなっている場合が考えられ、この場合、例外などの異常が起きる可能性があります。
// メソッドの戻り値が、LINQのメソッドが戻す結果のコレクション
IEnumerable<int> GetResults(IEnumerable<int> source)
{
using var someDisposable = new SomeDisposable();
return source.Where(x => /* someDisposableを利用する処理 */ );
// メソッドから戻る時点で、someDisposableに対して、Dispose()が呼ばれる
}
var results = GetResults([1, 2, 3]); // 上記のメソッドを呼ぶ
foreach (var item in results) // someDisposableが、Dispose()されていて、利用できない
{
Console.WriteLine(item);
}
遅延実行による問題を防ぐ方法
上記のような問題があり、遅延実行を防ぎたい場合、LINQのメソッドが戻した結果のコレクションに対してToArray
メソッドやToList
メソッドを呼び、全要素を生成してしまうといいでしょう。
ToArray
メソッドやToList
メソッドを呼んだ結果の配列やList<T>
には、生成済みの要素が入ります。要素を取り出す際は、生成済みの要素が取り出されるだけですから、遅延実行に伴う問題は起こりません。
int[] source = [1, 2, 3];
var results = source.Select(x => Guid.NewGuid())
.ToArray(); // ここで全要素が生成され、配列に入る
foreach (var item in results) // ここで取り出す要素と
{
Console.WriteLine(item);
}
foreach (var item in results) // ここで取り出す要素は、同じ
{
Console.WriteLine(item);
}
// メソッドの戻り値が、LINQのメソッドが戻す結果のコレクション
IEnumerable<int> GetResults(IEnumerable<int> source)
{
using var someDisposable = new SomeDisposable();
return source.Where(x => /* someDisposableを利用する処理 */ )
.ToArray(); // ここで全要素が生成され、配列に入る
}
var results = GetResults([1, 2, 3]); // 上記のメソッドを呼ぶ
foreach (var item in results) // 配列から要素を取り出す。someDisposableは関係ない
{
Console.WriteLine(item);
}