- Home /
Out of sync error when iterating over a Dictionary
So, I am getting the following error (that I have not yet run into) when iterating over a Dictionary in my code. It's a simple piece of code that iterates through the dictionary and resets the player scores inside it. Here's there code
void resetScore () {
foreach (KeyValuePair<string, int> pair in points) {
points[pair.Key] = 0;
}
}
And here is the error
InvalidOperationException: out of sync
System.Collections.Generic.Dictionary`2+Enumerator[System.String,System.Int32].VerifyState () (at /Applications/buildAgent/work/3df08680c6f85295/mcs/class/corlib/System.Collections.Generic/Dictionary.cs:912)
System.Collections.Generic.Dictionary`2+Enumerator[System.String,System.Int32].MoveNext () (at /Applications/buildAgent/work/3df08680c6f85295/mcs/class/corlib/System.Collections.Generic/Dictionary.cs:835)
gameManager.resetScore () (at Assets/code/gameManager.cs:85)
gameManager.turnRound () (at Assets/code/gameManager.cs:44)
gameManager.scoreUpdate (System.String scoringPlayer, Int32 scoreValue) (at Assets/code/gameManager.cs:80)
goal.OnTriggerEnter (UnityEngine.Collider obj) (at Assets/code/goal.cs:10)
I'm not entirely sure what this error means or how to go about dealing with it. Any help is most appreciated.
Answer by Julien-Lynge · Mar 03, 2013 at 07:13 AM
One of the restrictions of a foreach loop is that you can't change the underlying thing you're iterating over. If you change a dictionary while you're iterating through it, the program has no idea if it's getting out of sync and could end up for instance in an infinite loop.
Instead, you can get a list of the keys in the dictionary and iterate over that list. Since you won't be changing that list, just using it to change the dictionary, there's no chance of getting out of sync.
I don't understand this restriction at all. Surely you can reset the values of existing items in a dictionary in a loop? I can appreciate this restriction if you were removing items from the dictionary but what can setting the values of existing items do to cause problems? What am I missing here?
This restriction is due to the way that enumerables work in C#, which is not as straightforward as it looks.
Whenever you call a foreach loop, what C# is doing under the hood is creating a state machine to iterate through. Essentially, it's creating a little $$anonymous$$i-program on the fly that you can interact with, and that can return a sequence of items.
If you use a foreach loop with a dictionary, as in your example, then you're invalidating the previously created state machine, and C# is forced to start over with a new state machine. It considers this an error, as it can very easily lead to infinite loops, and it doesn't allow you to do so.
In the case of a Dictionary, you're right: C# could have architected this differently, and added hard-coded error checking and such to ensure that it doesn't get in trouble. However, C# can enumerate a lot of different things - many types of collections, but also any class marked as IEnumerable. Thus, they came up with a solution that is highly extensible and quite powerful once you start getting into really advanced C# program$$anonymous$$g - but at the cost of not being able to change the state machine you're iterating over.
It's not a restriction of C# but a restriction that has been purposefully implemented in the Dictionary and List enumerator. Common problems that could arise from not catching a change are those:
foreach(var i in someList)
{
someList.Add(i+5);
}
Depending how the iteration is implemented in the enumerator this could either result in twice the elements (if the initial element count is stored in the beginning) or will result in an infinite loop as the count grows as you iterate over the elements so you never reach an end.
Now if you think: "Just implement it like in the first case, store the count in the beginning". However this will have a different problem:
foreach(var i in someList)
{
someList.Remove(i);
}
Here the internal loop would try to access elements that doesn't exist anymore. Also it would basically skip every second element. Since we delete the first element during the first iteration the second element becomes the new first. So during the second iteration you will access the previous third element which is now the second. If you store the count in the beginning the list will end too early.
No matter how you put it, as soon as you change something in the collection it makes no sense to continue. That's why the List has an internal "version" integer value that is increased whenever you change the collection (Add, Remove, Clear, RemoveAt, ...). The enumerator object stores the current version when it's created. Whenever the version of the List and the stored version differ an exception is thrown.
This method is executed before each iteration step:
private void VerifyState()
{
if (this.l == null)
{
throw new ObjectDisposedException(base.GetType().FullName);
}
if (this.ver != this.l._version)
{
throw new InvalidOperationException("Collection was modified; enumeration operation may not execute.");
}
}
the variable "l" is the reference to the List and "ver" is the locally stored version of the list and of course "l._version" is the current version that is stored inside the List instance in a private variable. Note, this method is from the $$anonymous$$ono implementation that Unity uses. $$anonymous$$icrosoft does the check directly inside $$anonymous$$oveNext.
Answer by furic · Mar 20, 2015 at 04:57 AM
Added to @Julien's answer, instead of using:
foreach (string s in dict.Keys) {
dict [s] = ...
Use:
List<string> keys = new List<string> (dict.Keys);
foreach (string key in keys) {
dict [key] = ...
Iterating over the keys alone gives the same error, this is what's required. Thanks @furic
Answer by Trojaner · Aug 04, 2016 at 08:09 PM
foreach (string s in dict.Keys.ToList()) {
dict [s] = ...
Would be the simplest solution I guess
I don't see method ToList()
available from $$anonymous$$eys
Hey @SaSha_$$anonymous$$, the ToList() method is an extension method provided by the LINQ library. To enable it, you have to be 'using System.Linq;' at the top of your file.
You should be able to Type in ...$$anonymous$$eys.ToList(), and then right click, and your IDE will allow you to add the using statement that way.
Answer by AndyMartin458 · Jul 04, 2015 at 01:09 AM
Before I saw @furic 's answer to create a list out of the keys, I did this. Thought someone might find it useful.
Item[] items = new Item[m_dict.Count];
m_dict.Values.CopyTo(items, 0);
foreach (Item item in items)
{
Destroy(item);
m_dict.remove(item);
}
Your answer
Follow this Question
Related Questions
Multiple Cars not working 1 Answer
Distribute terrain in zones 3 Answers
Making a bubble level (not a game but work tool) 1 Answer
An OS design issue: File types associated with their appropriate programs 1 Answer
C# Dictionary Init 2 Answers