1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LINQの落とし穴(遅延実行)

Posted at

参考

この記事は、以下の動画を参考にしています。
詳しくは、動画をご覧ください。

遅延実行(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);
}
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?