Code Fix

中級

Collection was modified の原因と直し方

foreach中にList等のコレクションの要素数を変えると発生します。反復子(イテレータ)が内部状態の不整合を検知する仕組みです。

エラーメッセージの読み方

Unhandled exception. System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
System
名前空間 — System なら標準の基本例外です
InvalidOperationException
例外クラス — 何が起きたか。ここを検索するのが最短です
Collection was modified; enumeration operation may not execute.
詳細メッセージ — どの値が問題だったか
at Program.Main(String[] args) in Program.cs:line 4
発生箇所 — スタックトレースの先頭行

このエラーが出る典型パターン

パターン1

 1  class Program {
 2      static void Main(string[] args) {
 3          var nums = new System.Collections.Generic.List<int> { 1, 2, 3 };
 4          foreach (int n in nums) {
 5              if (n == 2) {
 6                  nums.Remove(n)             ;
                                 ^
 7              }
 8          }
 9      }
10  }
Unhandled exception. System.InvalidOperationException: Collection was modified; enumeration operation may not execute. at Program.Main(String[] args) in Program.cs:line 4

foreach中にコレクションの要素数を変えると、内部の反復子が不整合を検知して例外を投げます。

直し方: nums.Remove(n) を System.Console.WriteLine(n) にします。

この問題を解いてみる →

広告
広告スロット(未設定)

パターン2

 1  class Program {
 2      static void Main(string[] args) {
 3          var nums = new System.Collections.Generic.List<int> { 1, 2, 3 };
 4          foreach (int n in nums) {
 5              if (n == 1) {
 6                  nums.Add(n * 10)                ;
                                    ^
 7              }
 8          }
 9      }
10  }
Unhandled exception. System.InvalidOperationException: Collection was modified; enumeration operation may not execute. at Program.Main(String[] args) in Program.cs:line 4

要素を追加する場合も同じです。反復中はコレクションの形を変えてはいけません。

直し方: nums.Add(n * 10) を System.Console.WriteLine(n * 10) にします。

この問題を解いてみる →

パターン3

 1  class Program {
 2      static void Main(string[] args) {
 3          var nums = new System.Collections.Generic.List<int> { 1, 2, 3 };
 4          foreach (int n in nums) {
 5              if (n == 3) {
 6                  nums.RemoveAt(0)                 ;
                                    ^
 7              }
 8          }
 9      }
10  }
Unhandled exception. System.InvalidOperationException: Collection was modified; enumeration operation may not execute. at Program.Main(String[] args) in Program.cs:line 4

RemoveAtでインデックス指定で消す場合も同様です。安全に消したいならToList()でコピーしてから反復します。

直し方: nums.RemoveAt(0) を System.Console.WriteLine(nums[0]) にします。

この問題を解いてみる →

パターン4

 1  class Program {
 2      static void Main(string[] args) {
 3          var vals = new System.Collections.Generic.List<int> { 5, 10, 15 };
 4          foreach (int v in vals) {
 5              if (v == 10) {
 6                  vals.Remove(v)             ;
                                 ^
 7              }
 8          }
 9      }
10  }
Unhandled exception. System.InvalidOperationException: Collection was modified; enumeration operation may not execute. at Program.Main(String[] args) in Program.cs:line 4

foreach中に要素数を変えると、反復子が不整合を検知して例外を投げます。

直し方: vals.Remove(v) を System.Console.WriteLine(v) にします。

この問題を解いてみる →

パターン5

 1  class Program {
 2      static void Main(string[] args) {
 3          var tags = new System.Collections.Generic.List<string> { "a", "b" };
 4          foreach (string t in tags) {
 5              if (t == "a") {
 6                  tags.Add("c")              ;
                                 ^
 7              }
 8          }
 9      }
10  }
Unhandled exception. System.InvalidOperationException: Collection was modified; enumeration operation may not execute. at Program.Main(String[] args) in Program.cs:line 4

要素を追加する場合も同じです。反復中はコレクションの形を変えてはいけません。

直し方: tags.Add("c") を System.Console.WriteLine(t) にします。

この問題を解いてみる →

よくある誤解

forループなら添字で直接アクセスするので気づきにくいですが、foreachは内部で反復子を使っており、反復中の追加・削除に非常に敏感です。

実務での勘所

安全に走査しながら要素を削除したい場合の実務での定石は、foreach (var item in original.ToList())のように、走査対象を元のコレクションのコピー(ToList()で作った新しいリスト)に切り替え、削除自体は元のコレクションに対して行う方法です。あるいは、末尾から先頭に向かって添字を減らしながら回すfor (int i = list.Count - 1; i >= 0; i--)というループなら、ある位置を削除しても、まだ訪れていない前方の要素の位置がずれないため安全に削除できます。

演習をはじめる

関連するエラー

広告
広告スロット(未設定)