今となっては 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
メソッドを使うメリットはない、というのが私の主張です。