タイトルそのままです。C#でListの内の要素を削除したときに時々例外が発生するらしくアプリが落ちることがありました。常にではなく「時々」です。このようなバグは改善するのが難しいです。

時々例外が発生する理由は「C#でListの内の要素をforeachのなかで削除しようとしていたから」です。

foreach文のなかでリストの要素の追加と削除は不可

まずは明らかにダメな例です。

System.InvalidOperationException: コレクションが変更されました。列挙操作は実行されない可能性があります。

このような例外が発生します。foreach文のなかではリストの要素を追加したり削除することはできないのです。

単純にリストのなかから偶数の要素を取り除きたいだけならforeach文を使わずにこんな方法でもできます。

同期処理なら鳩でもミスはしない

ただ別のところでforeach文を使う場合もあるはずです。これが通常の同期処理(複数のタスクを実行する際に一つずつ順番にタスクが実行される)場合であれば問題はありません。たとえば上記のコードであれば、リストから要素を取り除いたあとにforeach文を使ったとしてもなんの問題もありません。

非同期処理をしているときは注意!

ところが非同期処理というものを覚えてこれを多用しはじめると問題がおきます。別のところでforeach文でリストの要素を取得して処理をしているのに、非同期でおこなわれている処理で要素が削除されたらどうなるのでしょうか?

つまりFunc1メソッドが呼び出されてから10秒以内にFunc2メソッドが呼び出してしまった場合です。これは実験してみればわかりますが、さきほどと同じ例外(System.InvalidOperationException: コレクションが変更されました。列挙操作は実行されない可能性があります)が発生します。

非同期処理時の問題回避策?

ではリストに格納されているデータをforeach文で処理したいけど例外が発生しては困るという場合、どうすればいいのでしょうか?

こんな方法はどうでしょうか?

別のリストに要素をコピーしてからループを回します。この場合、Vsの要素が削除されてもvsの要素に変更はないので例外は発生しません。

時間的ロスはどれくらい?

ところで別のリストに要素をコピーするのにどれくらい時間がかかるのでしょうか?

Func1メソッドとFunc2メソッドの実行にかかる時間を比較してみましたが、コピーをしているぶんFunc2メソッドのほうが時間がかかります。10万件のリストに格納された整数の総和を計算するのにかかった時間はFunc1メソッドが0.7~0.8ミリ秒、Func2メソッドが0.9~1.1ミリ秒でした。10万件あっても1ミリ秒に満たない差なので無視してもよいと思います。