[.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底下執行,結果如下:
而一模一樣的程式,在3.5底下執行,結果卻是可以正常運作:
從上面的結果可以推估,在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,都會如同預期般出錯。
結論
簡單幾點結論:
- 不管使用的framework為哪一版,都不應該在foreach中修改集合或項目。
-
若程式中有使用
OrderedDictionary
,在升級到4.0的時候,要小心這樣的問題發生,畢竟有很多是前人的包袱。
blog 與課程更新內容,請前往新站位置:http://tdd.best/