【C#】System.InvalidOperationException: コレクションが変更されました

C#

C#プログラミングにおいて、コレクションの操作中にそのコレクションを変更しようとすると、「System.InvalidOperationException: コレクションが変更されました。列挙操作は実行されない可能性があります。」というエラーが発生することがあります。このエラーは、主にforeachループを使用してコレクションを列挙している最中に、そのコレクションを変更(追加、削除など)しようとしたときに発生します。

本記事では、System.InvalidOperationException が、どのようなコードで発生し、また、どのようにすることで防ぐことができるかを紹介します。

エラーが発生する例

まずは、このエラーがどのような状況で発生するかを示すサンプルコードです。

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        // 文字列のリストを初期化
        List<string> fruits = new List<string> { "Apple", "Banana", "Cherry" };

        // リストを列挙しながら要素を削除しようとする
        foreach (var fruit in fruits)
        {
            // 先頭の文字が"B"の要素を削除
            if (fruit.StartsWith("B")) 
            {
                fruits.Remove(fruit); // ここでエラーが発生します
            }
        }

        // 削除した結果を表示
        foreach (var fruit in fruits)
        {
            Console.WriteLine(fruit);
        }
    }
}

このコードでは、fruitsリストから”Banana”を削除しようとしていますが、foreachループの内部でコレクションを変更しているため、InvalidOperationExceptionが発生します。

対策1:for文でリストの末尾から処理する

この方法では、コレクションを逆方向からループすることで、要素の削除時に後続の要素のインデックスが変わることを避けることができます。これにより、安全に要素を削除できるようになります。

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        List<string> fruits = new List<string> { "Apple", "Banana", "Cherry" };

        // 逆方向でループを回す
        for (int i = fruits.Count - 1; i >= 0; i--)
        {
            // 先頭の文字が"B"の要素を削除
            if (fruits[i].StartsWith("B"))
            {
                fruits.RemoveAt(i); // 安全に要素を削除
            }
        }

        // 削除した結果を表示
        foreach (var fruit in fruits)
        {
            Console.WriteLine(fruit);
        }
    }
}

対策2:削除対象を別リストに格納して後で削除

別のリストに削除対象を格納しておき、後のループで実際の削除を行います。

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        List<string> fruits = new List<string> { "Apple", "Banana", "Cherry" };
        List<string> itemsToRemove = new List<string>(); // 削除する要素を追加する一時リスト

        // 削除する要素を一時リストに追加
        foreach (var fruit in fruits)
        {
            // 先頭の文字が"B"の要素を削除
            if (fruit.StartsWith("B"))
            {
                itemsToRemove.Add(fruit);
            }
        }

        // 一時リストに基づいて要素を削除
        foreach (var item in itemsToRemove)
        {
            fruits.Remove(item);
        }

        // 削除した結果を表示
        foreach (var fruit in fruits)
        {
            Console.WriteLine(fruit);
        }
    }
}
タイトルとURLをコピーしました