[.NET]OrderedDictionary在migration到.net framework 4.0的問題

  • 4595
  • 0

[.NET]OrderedDictionary在migration到.net framework 4.0的問題

前言

先前同事在使用歷史悠久的底層時,從.net framework 3.5升級到.net framework 4.0的時候,發生了奇怪的異常。很確定的是,一樣的程式,在3.5是正常運作的,在4.0就會錯。

請同事將錯誤訊息貼出來,錯誤訊息如下:

System.InvalidOperationException: 集合已修改; 列舉作業可能尚未執行。

看到exception,其實寫的挺清楚的,就是有集合在列舉的時候,集合的項目被修改了。

 

Trouble Shooting

檢查了一下source code,有問題的code就類似這樣的寫法:


private static void ModifyForeachItem()
        {
            //在.net framework 2.0/3.5中,不會出現exception
            //但在.net framework 4.0中,會出現exception
            try
            {
                var o = new OrderedDictionary();
                for (int i = 0; i < 3; i++)
                {
                    o.Add(i, string.Format("{0}_{1}", i.ToString(), "yahoo"));
                }

                foreach (DictionaryEntry item in o)
                {
                    o[item.Key] = DateTime.Now.ToString();
                }

                foreach (DictionaryEntry dic in o)
                {
                    Console.WriteLine("key:{0}, value:{1}", dic.Key.ToString(), dic.Value.ToString());
                }

                Console.WriteLine("no exception");
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
                //throw;
            }
        }

問題原因就如同exception上所描述的,在第15行的位置,在foreach中,修改了o的集合。知道了原因,解決方式就沒什麼困難的。假設先用個笨方法,也就是新增一個集合來存放foreach中要改變的值,如下:


        private static void ModifyForeachItemWithTempCollection()
        {
            var o = new OrderedDictionary();
            for (int i = 0; i < 3; i++)
            {
                o.Add(i, string.Format("{0}_{1}", i.ToString(), "yahoo"));
            }

            var result = new OrderedDictionary();
            foreach (DictionaryEntry item in o)
            {
                result[item.Key] = DateTime.Now.ToString();
            }

            foreach (DictionaryEntry dic in result)
            {
                Console.WriteLine("key:{0}, value:{1}", dic.Key.ToString(), dic.Value.ToString());
            }

            Console.WriteLine("no exception");
        }

 

延伸討論

讓我納悶的不是4.0出現的exception,而是為什麼在3.5不會出錯。這邊列出實驗的程式碼:


        private static void Main(string[] args)
        {
            ModifyForeachItem();

            Console.WriteLine();

            Console.WriteLine("不直接修改foreach集合的方式:");
            ModifyForeachItemWithTempCollection();
        }

所以做了個實驗,用上面的程式碼在4.0底下執行,結果如下:

image

而一模一樣的程式,在3.5底下執行,結果卻是可以正常運作:

image

從上面的結果可以推估,在3.5與4.0裡面,OrderedDictionary在foreach中,行為有所不同。(在3.5中,在foreach中修改OrderedDictionary並不會造成錯誤)

為了確定是3.5/4.0的問題,還是OrderedDictionary的問題,所以這邊把OrderedDictionary換成Dictionary<int, string>來做測試,程式碼如下:


    internal class Program
    {
        private static void Main(string[] args)
        {
            ModifyForeachItem();

            Console.WriteLine();

            Console.WriteLine("不直接修改foreach集合的方式:");
            ModifyForeachItemWithTempCollection();
        }

        private static void ModifyForeachItem()
        {            
            try
            {
                var o = new Dictionary<int, string>();
                for (int i = 0; i < 3; i++)
                {
                    o.Add(i, string.Format("{0}_{1}", i.ToString(), "yahoo"));
                }

                foreach (var item in o)
                {
                    o[item.Key] = DateTime.Now.ToString();
                }

                foreach (var dic in o)
                {
                    Console.WriteLine("key:{0}, value:{1}", dic.Key.ToString(), dic.Value.ToString());
                }

                Console.WriteLine("no exception");
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
                //throw;
            }
        }

        private static void ModifyForeachItemWithTempCollection()
        {
            var o = new Dictionary<int, string>();
            for (int i = 0; i < 3; i++)
            {
                o.Add(i, string.Format("{0}_{1}", i.ToString(), "yahoo"));
            }

            var result = new Dictionary<int, string>();
            foreach (var item in o)
            {
                result[item.Key] = DateTime.Now.ToString();
            }

            foreach (var dic in result)
            {
                Console.WriteLine("key:{0}, value:{1}", dic.Key.ToString(), dic.Value.ToString());
            }

            Console.WriteLine("no exception");
        }
    }

測試後發現,不論在3.5或4.0,執行結果都會出現集合已修改的exception。這邊我也使用了List<T>去測試,結果與Dictionary<TKey, TValue>相同,不論在2.0, 3.5, 4.0,都會如同預期般出錯。

 

結論

簡單幾點結論:

  1. 不管使用的framework為哪一版,都不應該在foreach中修改集合或項目。
  2. 若程式中有使用OrderedDictionary,在升級到4.0的時候,要小心這樣的問題發生,畢竟有很多是前人的包袱。

blog 與課程更新內容,請前往新站位置:http://tdd.best/