C#
 Computer >> コンピューター >  >> プログラミング >> C#

C#の「コレクションが変更されました。列挙操作は実行されない可能性があります」エラーの原因と解決策


C#で foreach ループを使ってコレクション(例:List<T>)を列挙している最中に、そのコレクションへ要素の追加や削除といった変更を加えると、実行時に以下の例外がスローされます。

Exception: Collection was modified; enumeration operation may not execute.
(例外:コレクションが変更されました。列挙操作は実行されない可能性があります。)

エラーが発生する理由

foreach は内部で列挙子(Enumerator)を取得し、それを使ってコレクションを先頭から順番に走査します。List<T> の列挙子は取得時点のコレクションの状態(バージョン)を記録しており、列挙中に要素が追加・削除されて構造が変化すると、その不一致が検出されます。これは要素の読み飛ばしや二重処理といった予期しない動作を防ぐための保護機構であり、結果として InvalidOperationException がスローされます。

エラーが発生するコード例

using System;
using System.Collections.Generic;
namespace DemoApplication {
    public class Program {
        static void Main(string[] args) {
            try {
                var studentsList = new List<Student> {
                    new Student {
                        Id = 1,
                        Name = "John"
                    },
                    new Student {
                        Id = 0,
                        Name = "Jack"
                    },
                    new Student {
                        Id = 2,
                        Name = "Jack"
                    }
                };
                foreach (var student in studentsList) {
                    if (student.Id <= 0) {
                        studentsList.Remove(student);
                    }
                    else {
                        Console.WriteLine($"Id: {student.Id}, Name: {student.Name}");
                    }
                }
            }
            catch(Exception ex) {
                Console.WriteLine($"Exception: {ex.Message}");
                Console.ReadLine();
            }
        }
    }
    public class Student {
        public int Id { get; set; }
        public string Name { get; set; }
    }
}

出力結果

Id: 1, Name: John
Exception: Collection was modified; enumeration operation may not execute.

上記の例では、studentsList を対象に foreach ループを実行しています。学生の Id が 0 以下の場合に studentsList.Remove(student) を呼び出して要素を削除していますが、この操作により列挙中にコレクションのサイズが変化し、実行時に例外が発生してしまいます。

解決策1:ToList() でコピーを列挙する

最も手軽な対処法は、foreach の開始時に ToList() を呼び出して元のリストのコピーを作成し、そのコピーを列挙することです。こうすれば、ループ内で元のリストを変更しても、列挙対象のコレクションには影響しません。

foreach (var student in studentsList.ToList())

ToList() を使った完全なコード例は以下の通りです。

using System;
using System.Collections.Generic;
using System.Linq;
namespace DemoApplication {
    public class Program {
        static void Main(string[] args) {
            var studentsList = new List<Student> {
                new Student {
                    Id = 1,
                    Name = "John"
                },
                new Student {
                    Id = 0,
                    Name = "Jack"
                },
                new Student {
                    Id = 2,
                    Name = "Jack"
                }
            };
            foreach (var student in studentsList.ToList()) {
                if (student.Id <= 0) {
                    studentsList.Remove(student);
                }
                else {
                    Console.WriteLine($"Id: {student.Id}, Name: {student.Name}");
                }
            }
            Console.ReadLine();
        }
    }
    public class Student {
        public int Id { get; set; }
        public string Name { get; set; }
    }
}

出力結果

Id: 1, Name: John
Id: 2, Name: Jack

今度は例外が発生せず、Id が 0 の Jack だけが正しく削除されました。

解決策2:逆順の for ループで削除する

インデックスでアクセスできる List であれば、後ろから前へ向かって走査する for ループを使う方法もあります。末尾側から削除していけば、未処理の要素のインデックスがずれないため、安全に要素を削除できます。

for (int i = studentsList.Count - 1; i >= 0; i--) {
    if (studentsList[i].Id <= 0) {
        studentsList.RemoveAt(i);
    }
}

解決策3:RemoveAll() を使う

条件に一致する要素をまとめて削除したい場合は、List<T>.RemoveAll() メソッドが便利です。ラムダ式で条件を指定するだけで、該当するすべての要素を1行で安全に削除できます。

studentsList.RemoveAll(s => s.Id <= 0);

まとめ

foreach ループの実行中にコレクション自体を変更すると、「Collection was modified; enumeration operation may not execute」例外が発生します。対処法としては、① ToList() でコピーを列挙する、② 逆順の for ループで削除する、③ RemoveAll() で一括削除する、のいずれかを状況に応じて選びましょう。

  1. 0x0000007Fエラーの原因と解決方法を徹底解説

    0x0000007Fエラーは、いわゆる「ブルースクリーン(BSOD)」エラーの一種です。特定の操作を行うとコンピュータが突然再起動し、青い画面が表示されるのが特徴で、主にWindowsの「カーネル」に関する問題が原因で発生します。この記事では、エラーの発生メカニズムと、レジストリやシステム設定を用いた具体的な解決策を順番にご紹介します。 0x0000007Fエラーとは? 0x0000007Fエラーは、カーネルモードのドライバを処理するための十分なカーネルスタック領域が確保できない場合に発生します。カーネルはOSの中核を担う最も重要なコンポーネントであり、アプリケーションとハードウェアレベル

  2. Windows 10で「ネットワークパスが見つかりません」エラーを解決する4つの方法

    Microsoftでは、同じネットワーク上のコンピュータ同士がリモートアクセスしてファイルをやり取りできるようになっています。このデータやリソースの共有機能は、2台以上のデバイスを同時に使用する際に非常に便利です。しかし、利用者が問題に遭遇することもあり、その中でもよく見られるのが「ネットワークパスが見つかりません」というエラーです。本記事では、この問題を解決するための4つのトラブルシューティング手法を詳しく解説します。 Windowsで「ネットワークパスが見つかりません」エラーを修正する方法 方法1:TCP/IP NetBIOS Helperサービスを再起動する リモートデバイスに接続してフ