Problem
I’m attempting to make a pie chart out of a dictionary. I’d like to clean up the data before displaying the pie chart. I’m going to put any pie slices that would make up less than 5% of the pie in a “Other” pie slice. However, at runtime, I receive a Collection was updated; enumeration operation may not perform exception.
I see why you can’t add or remove elements from a dictionary while iterating through it. However, I’m not sure why you can’t just modify the value of an existing key inside the foreach loop.
Any suggestions re: fixing my code, would be appreciated.
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);
Asked by Aheho
Solution #1
Setting a value in a dictionary changes its internal “version number,” invalidating the iterator as well as any other iterators linked to the keys or values collection.
I get your reasoning, however it would be strange if the values collection could change in the middle of an iteration – especially because there is only one version number.
The standard way to fix this is to either clone the collection of keys beforehand and iterate over the copy, or loop over the original collection while keeping a list of modifications to apply when you’ve finished iterating.
For example:
Copying keys first
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;
}
}
Or…
Creating a modification list
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;
}
Answered by Jon Skeet
Solution #2
In the foreach loop, call ToList(). This eliminates the requirement for a transient variable copy. Linq, which has been accessible since, is used. 3.5 on the net.
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;
}
}
Answered by DIG
Solution #3
You are modifying the collection in this line:
You’re essentially deleting and reinserting stuff at that point (at least as far as IEnumerable is concerned).
It’s fine if you update a member of the value you’re saving, but you’re editing the value itself, which IEnumberable doesn’t like.
The method I used was to remove the foreach loop and replace it with a for loop. A basic for loop will ignore changes that aren’t likely to affect the collection.
Here’s how you could go about doing it:
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;
}
}
Answered by CodeFusionMobile
Solution #4
You can’t modify the keys nor the values directly in a ForEach, but you can modify their members. For example, this should work:
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 } );
Answered by Jeremy Frey
Solution #5
Why not simply run some Linq queries against your dictionary and then bind your graph to the results? …
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);
}
Answered by Scott Ivey
Post is based on https://stackoverflow.com/questions/1070766/editing-dictionary-values-in-a-foreach-loop