C# - foreach Exception "集合已修改; 列舉作業可能尚未執行" 解決方法

限量今天在寫到一個功能時需要從List中將符合條件的Item刪掉,第一次迴圈能夠刪掉,但執行第二次迴圈時竟然出現Exception,Message為"集合已修改; 列舉作業可能尚未執行"(英文為"Collection was modified; enumeration operation may not execute.")。

原始程式:


var sourceData = new List() 
{
    1, 2, 3, 2, 2, 5, 4, 11, 10, 6, 1
};

foreach ( var o in sourceData )
{
    if ( o == 2 ) sourceData.Remove( o );
}





Try許多種方式後來限量決定Google一下發現原來是限量錯誤使用FOREACH的用法,在MSDN上有一段話 "The foreach statement is used to iterate through the collection to get the information that you want, but can not be used to add or remove items from the source collection to avoid unpredictable side effects. If you need to add or remove items from the source collection, use a for loop."。簡而言之,就是說FOREACH是ReadOnly的,你無法在迴圈裡改變來源Collection。

雖然MSDN說可以用For去做,但限量就是偏偏不用For,因為想一想就覺得程式碼應該一堆,所以針對這種狀況目前限量有三種解法,如下:

解法1


第1種解法為利用一個與來源Collection相同的暫時LIST來當作來源Collection操作,等操作完成後再將這個暫時LIST取代掉來源Collection

var tmpData = new List<int>( sourceData );
foreach ( var o in sourceData )
{
    if ( o == 2 )
    tmpData.Remove( o );
}
sourceData = tmpData;

這個方法可行是因為造出一個資料與來源Collection相同的目標,且與來源Collection沒有Reference的關係。就因為資料相同,所以只要在來源Collection找到相符的Item代表目標也找到符合的Item。

解法2

第2種解法是將FOREACH所讀取的來源資料轉成Array,這樣Remove就沒有問題了。

foreach ( var o in sourceData.ToArray() )
{
    if ( o == 2 ) sourceData.Remove(o);
}

這種解法與第1種原理類似,因為ToArray()之後會造出一個新的Collection,與來源Collection資料相同,但不一樣的是這裡FOREACH是以造出的Collection當作來源,與第1種方式的角色關係互換,這樣就可以少去完成後再取代的動作。

解法3


第3種解法為利用LINQ To Entity來刪除全部符合條件的元素,使用RemoveAll可以直接將來源資料中符合條件的Item刪除。

sourceData.RemoveAll(o => o == 2);

別看只有一行而已,其實裡頭做的事可多了,包含用迴圈到Array裡移除符合的Item,然後在進行Array的Copy與Clear。

對於限量這麼喜歡用LINQ To Entity的人當然選用解法3,畢竟只用一行就解決了,而且還可以知道刪除的個數。總之,追根究底還是因為錯誤使用程式語法,看來要仔細翻翻語法的SPEC後再使用。



參考來源:

How to Modify a Collection During Iteration in C#
MSDN - foreach, in (C# Reference)

留言

張貼留言