87
94

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

なぜ List<T>.ForEach は使うべきでないか

Posted at

今となっては List<T>.ForEach メソッドは使うべきではありません。オレオレ IEnumerable<T>.ForEach 拡張メソッドも同様です。代わりに foreach 文を使用しましょう。

以下に理由を述べます。

  • continue, break が使えない
  • yield return ができない
  • 非同期処理を待機、例外処理できない
  • デリゲート呼び出しのコストがかかる

continue, break が使えない

当然ではありますが continue, break 文が使えません。(使いませんとか言わないでください

できる
foreach (var x in xs)
{
    if (x == 0) break;
    Console.WriteLine(x);
}
できない
xs.ForEach(x =>
{
    // if (x == 0) break; // break できない
    Console.WriteLine(x);
});

継続を駆使して continue や break を使えるようにする方法も無くはないですが、既に記事は消えてしまったようです。。。
(リンク先の記事、理解したかったのに理解する前に消えてしまった。。。)

まぁ、そこまでするなら素直に foreach 文使いましょう。

yield return ができない

ラムダ式の制約で yield return ができません。
……ということではなく、そもそもラムダ式で yield return できたところで、それはデリゲート自体の戻り値を表すことになってしまうので、どちらにしても yield return ができません。

できる
public IEnumerable<Hoge> GetHoges()
{
    yield return a;
    foreach (var x in xs)
    {
        yield return x;
    }
}
できない
public IEnumerable<Hoge> GetHoges()
{
    yield return a;
    xs.ForEach(x =>
    {
        // yield return x; // できない(できたとしても意味が違う)
    });
}

非同期処理を待機、例外処理できない

処理に非同期メソッド呼び出しが含まれる場合は悲惨です。各要素に対する処理の部分はどうやっても async void にしかならない ⇒ 気付かず意図しない処理順で処理が実行されてしまう可能性があります。

意図通り動く
await a.DoAsync();
foreach (var x in xs)
{
    await x.DoAsync();
}
await b.DoAsync();
おそらく意図通りではない
await a.DoAsync();
xs.ForEach(async x =>
{
    await x.DoAsync();
});
await b.DoAsync();

後者は各要素の x.DoAsync() が並列に実行されてしまう可能性があります。また、全要素の処理が終わる前に b.DoAsync() 呼び出しが呼び出されてしまう可能性があります。

さらに、x.DoAsync() 呼び出しで例外が発生した場合、外側のコードを try-catch で囲っていても catch されません。

これらは async x => await x.DoAsync()async void なメソッドになるためです(ForEach メソッドの引数は Action<T> なので戻り値は void)。Task が戻ってこないため待機も例外処理もできません。

async void の恐ろしさについては asyncの落とし穴Part3, async voidを避けるべき100億の理由 を参照してください。100億の理由が4500字足らずで説明されています。流石 neuecc 先生すごい。

デリゲート呼び出しのコストがかかる

foreach 文に比べ、ForEach メソッドは各要素に対してデリゲートを呼び出すので多少のコストがかかります。

まぁそれがネックになるケースも少ないとは思いますが……(まだ実測してないので、実測したら追記します)

まとめ

上記のように ForEach メソッドにはデメリットがいっぱいです。
じゃあメリットは?というと、メソッドチェーンの最後につなげて書けるので書きやすい、場合によっては読みやすい、くらいでしょうか。

正直にネタばらすと、上記の問題は限定的な場面でしか問題にならない内容ではあります。が、そこで毎回問題ないかどうかを判断してまで ForEach メソッドを使うメリットはない、というのが私の主張です。

87
94
11

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
87
94

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?