2009-07-01 81 views
148

我正在嘗試從字典中構建餅圖。在展示餅圖之前,我想整理一下數據。我將刪除所有餅圖片的比例不到5%,然後放入「其他」餅圖片。但是,我在運行時遇到了Collection was modified; enumeration operation may not execute異常。在foreach循環中編輯字典值

我明白爲什麼在迭代它們的時候不能在字典中添加或刪除項目。不過,我不明白爲什麼你不能簡單地改變foreach循環中現有鍵的值。

任何建議重新:修復我的代碼,將不勝感激。

Dictionary<string, int> colStates = new Dictionary<string,int>(); 
// ... 
// Some code to populate colStates dictionary 
// ... 

int OtherCount = 0; 

foreach(string key in colStates.Keys) 
{ 

    double Percent = colStates[key]/TotalCount; 

    if (Percent < 0.05) 
    { 
     OtherCount += colStates[key]; 
     colStates[key] = 0; 
    } 
} 

colStates.Add("Other", OtherCount); 

回答

204

設置在字典中的值更新其內部「版本號」 - 其無效迭代器,並與鍵或值集合相關的任何迭代器。

我確實看到了你的觀點,但同時如果值集合可能會改變中間迭代將會很奇怪 - 爲簡單起見,只有一個版本號。

修復此類事件的正常方法是事先複製密鑰集合並迭代副本,或迭代原始集合但保留一組修改,您將在完成後應用這些修改迭代。

例如:

複製第一項

List<string> keys = new List<string>(colStates.Keys); 
foreach(string key in keys) 
{ 
    double percent = colStates[key]/TotalCount;  
    if (percent < 0.05) 
    { 
     OtherCount += colStates[key]; 
     colStates[key] = 0; 
    } 
} 

或者......

創建修改的列表

List<string> keysToNuke = new List<string>(); 
foreach(string key in colStates.Keys) 
{ 
    double percent = colStates[key]/TotalCount;  
    if (percent < 0.05) 
    { 
     OtherCount += colStates[key]; 
     keysToNuke.Add(key); 
    } 
} 
foreach (string key in keysToNuke) 
{ 
    colStates[key] = 0; 
} 
+16

我知道這是舊的,但如果使用.NET 3.5(或是4.0?),您可以使用和濫用LINQ如下: foreach(string key in colStates.Keys.ToList()){...} – Machtyn 2015-03-16 20:57:51

+3

@Machtyn:當然 - 但問題是關於.NET 2.0,否則我肯定*會使用LINQ。 – 2015-03-16 21:10:25

1

你需要創造e從舊的新詞典而不是修改到位。 Somethine像(也遍歷KeyValuePair <,>而不是使用鍵查找:

int otherCount = 0; 
int totalCounts = colStates.Values.Sum(); 
var newDict = new Dictionary<string,int>(); 
foreach (var kv in colStates) { 
    if (kv.Value/(double)totalCounts < 0.05) { 
    otherCount += kv.Value; 
    } else { 
    newDict.Add(kv.Key, kv.Value); 
    } 
} 
if (otherCount > 0) { 
    newDict.Add("Other", otherCount); 
} 

colStates = newDict; 
0

聲明:我沒有做很多C#

您試圖修改存儲在該字典條目對象HashTable Hashtable只存儲一個對象 - DictionaryEntry的實例,改變Key或Value就足以改變HashTable並導致枚舉器失效。

你可以在循環之外完成:

if(hashtable.Contains(key)) 
{ 
    hashtable[key] = value; 
} 

首先創建一個您想要更改的值的所有鍵的列表,而不是遍歷該列表。

1

您不能修改集合,甚至不能修改值。您可以保存這些案例並在以後刪除它們。這將最終是這樣的:

 Dictionary<string, int> colStates = new Dictionary<string, int>(); 
     // ... 
     // Some code to populate colStates dictionary 
     // ... 

     int OtherCount = 0; 
     List<string> notRelevantKeys = new List<string>(); 

     foreach (string key in colStates.Keys) 
     { 

      double Percent = colStates[key]/colStates.Count; 

      if (Percent < 0.05) 
      { 
       OtherCount += colStates[key]; 
       notRelevantKeys.Add(key); 
      } 
     } 

     foreach (string key in notRelevantKeys) 
     { 
      colStates[key] = 0; 
     } 

     colStates.Add("Other", OtherCount); 
+0

您*可以*修改集合。你*不能*繼續使用迭代器來修改集合。 – user2864740 2017-03-28 22:39:09

17

要修改的集合中的這一行:

colStates [鍵] = 0;

通過這樣做,你基本上是刪除,並在該點重新插入事情(只要IEnumerable的是反正關注。

如果編輯成員要存儲的價值,這將是好的,但是你正在編輯這個值本身,IEnumberable不喜歡這個。

我使用的解決方案是消除foreach循環,只是使用for循環。 簡單的for循環將不會檢查你知道的變化不會影響收藏品。

這裏是你如何能做到這一點:

List<string> keys = new List<string>(colStates.Keys); 
for(int i = 0; i < keys.Count; i++) 
{ 
    string key = keys[i]; 
    double Percent = colStates[key]/TotalCount; 
    if (Percent < 0.05)  
    {   
     OtherCount += colStates[key]; 
     colStates[key] = 0;  
    } 
} 
+0

我使用for循環得到了這個問題。字典[索引] [鍵] =「abc」,但它回到初始值「xyz」 – 2017-09-25 13:41:08

3

不能直接修改鍵還是值在foreach,但可以修改其成員。例如,這應該工作:

public class State { 
    public int Value; 
} 

... 

Dictionary<string, State> colStates = new Dictionary<string,State>(); 

int OtherCount = 0; 
foreach(string key in colStates.Keys) 
{ 
    double Percent = colStates[key].Value/TotalCount; 

    if (Percent < 0.05) 
    { 
     OtherCount += colStates[key].Value; 
     colStates[key].Value = 0; 
    } 
} 

colStates.Add("Other", new State { Value = OtherCount }); 
3

如何只是在做一些LINQ查詢對你的字典,然後你的曲線結合的結果...

var under = colStates.Where(c => (decimal)c.Value/(decimal)totalCount < .05M); 
var over = colStates.Where(c => (decimal)c.Value/(decimal)totalCount >= .05M); 
var newColStates = over.Union(new Dictionary<string, int>() { { "Other", under.Sum(c => c.Value) } }); 

foreach (var item in newColStates) 
{ 
    Console.WriteLine("{0}:{1}", item.Key, item.Value); 
} 
+0

Linq只有在3.5中才可用嗎?我正在使用.net 2.0。 – Aheho 2009-07-01 19:28:36

+0

你可以使用它從2.0引用到System.Core.DLL的3.5版本 - 如果這不是你想要讓我知道的,我會刪除這個答案。 – 2009-07-01 19:45:55

2

如果你感覺如何?創意你可以做這樣的事情。向後循環查看字典以進行更改。

Dictionary<string, int> collection = new Dictionary<string, int>(); 
collection.Add("value1", 9); 
collection.Add("value2", 7); 
collection.Add("value3", 5); 
collection.Add("value4", 3); 
collection.Add("value5", 1); 

for (int i = collection.Keys.Count; i-- > 0;) { 
    if (collection.Values.ElementAt(i) < 5) { 
     collection.Remove(collection.Keys.ElementAt(i)); ; 
    } 

} 

肯定不完全相同,但你可能會感興趣反正...

44

呼叫在foreach循環的ToList()。這樣我們不需要臨時變量副本。它取決於自.Net 3.5以來的Linq。

using System.Linq; 

foreach(string key in colStates.Keys.ToList()) 
{ 
    double Percent = colStates[key]/TotalCount; 

    if (Percent < 0.05) 
    { 
     OtherCount += colStates[key]; 
     colStates[key] = 0; 
    } 
} 
0

可以使dict.Values名單副本,那麼您可以使用迭代List.ForEach lambda函數(或foreach循環,因爲之前提出的建議)。

new List<string>(myDict.Values).ForEach(str => 
{ 
    //Use str in any other way you need here. 
    Console.WriteLine(str); 
}); 
0

開始使用.NET 4.5,您可以用ConcurrentDictionary做到這一點:

using System.Collections.Concurrent; 

var colStates = new ConcurrentDictionary<string,int>(); 
colStates["foo"] = 1; 
colStates["bar"] = 2; 
colStates["baz"] = 3; 

int OtherCount = 0; 
int TotalCount = 100; 

foreach(string key in colStates.Keys) 
{ 
    double Percent = (double)colStates[key]/TotalCount; 

    if (Percent < 0.05) 
    { 
     OtherCount += colStates[key]; 
     colStates[key] = 0; 
    } 
} 

colStates.TryAdd("Other", OtherCount); 

然而要注意它的性能實際上是差很多,一個簡單的foreach dictionary.Kes.ToArray()

using System; 
using System.Collections.Concurrent; 
using System.Collections.Generic; 
using System.Linq; 
using BenchmarkDotNet.Attributes; 
using BenchmarkDotNet.Running; 

public class ConcurrentVsRegularDictionary 
{ 
    private readonly Random _rand; 
    private const int Count = 1_000; 

    public ConcurrentVsRegularDictionary() 
    { 
     _rand = new Random(); 
    } 

    [Benchmark] 
    public void ConcurrentDictionary() 
    { 
     var dict = new ConcurrentDictionary<int, int>(); 
     Populate(dict); 

     foreach (var key in dict.Keys) 
     { 
      dict[key] = _rand.Next(); 
     } 
    } 

    [Benchmark] 
    public void Dictionary() 
    { 
     var dict = new Dictionary<int, int>(); 
     Populate(dict); 

     foreach (var key in dict.Keys.ToArray()) 
     { 
      dict[key] = _rand.Next(); 
     } 
    } 

    private void Populate(IDictionary<int, int> dictionary) 
    { 
     for (int i = 0; i < Count; i++) 
     { 
      dictionary[i] = 0; 
     } 
    } 
} 

public class Program 
{ 
    public static void Main(string[] args) 
    { 
     BenchmarkRunner.Run<ConcurrentVsRegularDictionary>(); 
    } 
} 

結果:

   Method |  Mean |  Error | StdDev | 
--------------------- |----------:|----------:|----------:| 
ConcurrentDictionary | 182.24 us | 3.1507 us | 2.7930 us | 
      Dictionary | 47.01 us | 0.4824 us | 0.4512 us |