2009-07-01 80 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 |