或阿呆のブログ

Pythonを好んで使っているプログラマです。Ruby,Perl,PowerShell,VBAなどでもたまに書いています。最近はスロット放浪記やってます。。

C#:辞書をイテレータとしてループしている中で辞書を変更すると例外が発生。コレクションが変更されました。列挙操作は実行されない可能性があります。

Sample Code(サンプルコード)

  Dictionary<string, string> dic = new Dictionary<string, string>();
            dic.Add("1", "a");
            dic.Add("2", "b");
            dic.Add("3", "c");

            foreach (string key in dic.Keys)
            {
                if (dic.ContainsKey("1"))
                {
                    dic.Remove("1");
                }
            }

Result(実行結果)

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

理由はわからんでもない。イテレーターとして制御に使われているものが変更されたら、制御不可能になる。辞書をイテレーションとして使う場合は、その辞書は変更不可。というか、言語仕様として、制御の中ではコピーして使うようにしてくれよとか思う・・・。しかし、言語仕様なんでそうも言えず。

解決策としては、コレクションのキーをコピーしてイテレータにするという方法がある。しかし、コピーの方法にも注意されたい。

Sample Code(サンプルコード)

    Dictionary<string, string>.KeyCollection keyCol = dic.Keys;

これはダメ。リファレンスにも書いてあるように、これはキーの静的なコピーではない。元の辞書を参照している。そのため、keyColを変更すると、元の辞書も変更される。故に、先ほどのコードと同じで、ループ内で値を変更すれば、例外が発生する。


辞書をイテレーターとして使いたいのであれば、以下のようにコピーしてやればいい。以下の例では、keyのcount分の要素を確保したstring型の配列を生成し、その領域にコピーして使っている。それであれば、元の辞書には影響を与えないため、制御中に辞書を変更しても、制御自体には問題無い。

Sample Code(サンプルコード)

static void Main(string[] args)
        {
            Dictionary<string, string> dic = new Dictionary<string, string>();
            dic.Add("1", "a");
            dic.Add("2", "b");
            dic.Add("3", "c");

            string[] keycoll = new string[dic.Keys.Count];
            dic.Keys.CopyTo(keycoll,0);
            foreach (object key in keycoll)
            {
                if (dic.ContainsKey("1"))
                {
                    dic["1"] = "d";
                }
            }
        }