我正在尝试从字典中构建饼图。在我显示饼图之前,我想整理数据。我正在删除任何小于饼的5%的饼图,并将它们放入“其他”饼图中。但是我在运行时遇到Collection was modified; enumeration operation may not execute
异常。
我理解为什么在迭代它们时无法在字典中添加或删除项目。但是我不明白为什么你不能简单地改变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);
答案 0 :(得分:232)
在字典中设置值会更新其内部“版本号” - 这会使迭代器失效,以及与键或值集合关联的任何迭代器。
我确实看到了你的观点,但与此同时,如果值集合可能在迭代中间发生变化则会很奇怪 - 为简单起见,只有一个版本号。
修复此类事情的常规方法是预先复制密钥集合并遍历副本,或者迭代原始集合,但保留一系列更改,您将在完成迭代后应用这些更改
例如:
首先复制键
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;
}
答案 1 :(得分:60)
在ToList()
循环中调用foreach
。这样我们就不需要临时变量副本了。这取决于自.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;
}
}
答案 2 :(得分:19)
您正在修改此行中的集合:
colStates [key] = 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;
}
}
答案 3 :(得分:6)
您无法直接在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 } );
答案 4 :(得分: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);
}
答案 5 :(得分:3)
如果你有创意,你可以做这样的事情。在字典中向后循环以进行更改。
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)); ;
}
}
当然不完全相同,但你可能会感兴趣...
答案 6 :(得分:2)
您需要从旧的字词创建一个新的字典而不是在原地进行修改。 Somethine喜欢(也迭代KeyValuePair&lt;,&gt;而不是使用键查找:
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;
答案 7 :(得分: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);
答案 8 :(得分:1)
在 .NET 5 中,可以在枚举字典时更改字典项。
拉取请求是:Allow Dictionary overwrites during enumeration,问题是Consider removing _version++ from overwrites in Dictionary<TKey, TValue>。
现在您可以:
foreach (var pair in dict)
dict[pair.Key] = pair.Value + 1;
答案 9 :(得分:0)
免责声明:我没有做太多C#
您正在尝试修改存储在HashTable中的DictionaryEntry对象。 Hashtable只存储一个对象 - 您的DictionaryEntry实例。更改密钥或值足以更改HashTable并导致枚举器变为无效。
你可以在循环之外完成:
if(hashtable.Contains(key))
{
hashtable[key] = value;
}
首先创建一个您希望更改的值的所有键的列表,然后迭代遍历该列表。
答案 10 :(得分: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);
});
答案 11 :(得分: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 |
答案 12 :(得分:0)
关于其他答案,我想我要注意的是,如果您得到sortedDictionary.Keys
或sortedDictionary.Values
,然后用foreach
进行遍历,那么您也将按排序顺序进行学习。这是因为这些方法返回System.Collections.Generic.SortedDictionary<TKey,TValue>.KeyCollection
或SortedDictionary<TKey,TValue>.ValueCollection
对象,它们保持原始字典的种类。
答案 13 :(得分:0)
此答案用于比较两种解决方案,而不是建议的解决方案。
您可以使用for
循环,而不使用字典Count
来表示循环停止条件,而可以使用Keys.ElementAt(i)
来获得密钥。
for (int i = 0; i < dictionary.Count; i++)
{
dictionary[dictionary.Keys.ElementAt(i)] = 0;
}
在第一时间,我认为这样做会更有效率,因为我们不需要创建密钥列表。运行测试后,我发现for
循环解决方案效率低得多。原因是因为ElementAt
属性上的dictionary.Keys
是O(n),它从集合的开头开始搜索,直到到达第n个项目为止。
测试:
int iterations = 10;
int dictionarySize = 10000;
Stopwatch sw = new Stopwatch();
Console.WriteLine("Creating dictionary...");
Dictionary<string, int> dictionary = new Dictionary<string, int>(dictionarySize);
for (int i = 0; i < dictionarySize; i++)
{
dictionary.Add(i.ToString(), i);
}
Console.WriteLine("Done");
Console.WriteLine("Starting tests...");
// for loop test
sw.Restart();
for (int i = 0; i < iterations; i++)
{
for (int j = 0; j < dictionary.Count; j++)
{
dictionary[dictionary.Keys.ElementAt(j)] = 3;
}
}
sw.Stop();
Console.WriteLine($"for loop Test: {sw.ElapsedMilliseconds} ms");
// foreach loop test
sw.Restart();
for (int i = 0; i < iterations; i++)
{
foreach (string key in dictionary.Keys.ToList())
{
dictionary[key] = 3;
}
}
sw.Stop();
Console.WriteLine($"foreach loop Test: {sw.ElapsedMilliseconds} ms");
Console.WriteLine("Done");
结果:
Creating dictionary...
Done
Starting tests...
for loop Test: 2367 ms
foreach loop Test: 3 ms
Done