Why can not we change the values ​​of the dictionary when listing its keys?

class Program { static void Main(string[] args) { var dictionary = new Dictionary<string, int>() { {"1", 1}, {"2", 2}, {"3", 3} }; foreach (var s in dictionary.Keys) { // Throws the "Collection was modified exception..." on the next iteration // What up with that? dictionary[s] = 1; } } } 

I fully understand why this exception is thrown when enumerating a list - it seems reasonable to expect that during the enumeration the structure of the enumerated object will not change. However, has the meaning of the dictionary changed the meaning of its structure? In particular, the structure of his keys?

+25
dictionary c # enumeration invalidoperationexception
Oct 13 '09 at 20:28
source share
9 answers

I see where you come from. Most of the comments here do not notice that you are iterating over the list of keys, not the dictionary itself. If the .NET Framework programmers wanted it, they could quite easily distinguish between changes made to the dictionary structure and changes made to the values ​​in the dictionary. However, even when people sort through keys from a collection, they tend to get values ​​anyway. I suspect that the designers of the .NET Framework believed that if you repeat these values, you need to know that something changes from them, just like with any list. Either this, or they did not see it as an important enough problem to deserve the programming and maintenance that would be required to distinguish between one kind of change and another.

+5
Oct 13 '09 at 20:55
source share

Because values ​​and keys are stored as a pair. There is no separate structure for keys and values, but instead one structure that stores as a set of values ​​for a pair. When you change a value, this requires a change in one basic structure containing both keys and values.

Does the value of the value change by changing the order of the underlying structure? No. But this is implementation-specific information and the Dictionary<TKey,TValue> , correctly, is considered not to disclose this, allowing you to change the values ​​as part of the API.

+17
Oct 13 '09 at 20:30
source share

Thanks to Vitaly, I came back and looked again at the code, and it looks like this is a specific implementation decision to prohibit this (see below fragment). The dictionary retains a personal value called verrsion, which increases as the value of an existing element changes. When the enumerator is created, it marks the value at this time, then checks each call on MoveNext.

 for (int i = this.buckets[index]; i >= 0; i = this.entries[i].next) { if ((this.entries[i].hashCode == num) && this.comparer.Equals(this.entries[i].key, key)) { if (add) { ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_AddingDuplicate); } this.entries[i].value = value; this.version++; return; } } 

I do not know why this was necessary. You can still change the properties of the value, just do not assign it to the new value:

 public class IntWrapper { public IntWrapper(int v) { Value = v; } public int Value { get; set; } } class Program { static void Main(string[] args) { var kvp = new KeyValuePair<string, int>("1",1); kvp.Value = 17; var dictionary = new Dictionary<string, IntWrapper>(){ {"1", new IntWrapper(1)}, {"2", new IntWrapper(2)}, {"3", new IntWrapper(3)} }; foreach (var s in dictionary.Keys) { dictionary[s].Value = 1; //OK dictionary[s] = new IntWrapper(1); // boom } } } 
+6
Oct 13 '09 at 20:57
source share

Perhaps you just entered a new dictionary into a dictionary that would really change dictionary.Keys . Despite the fact that in this particular loop that will never happen, the operation [] as a whole can change the list of keys, therefore it is marked as a mutation.

+5
Oct 13 '09 at 20:31
source share

Indexer on Dictionary potentially an operation that can change the structure of a collection, as it will add a new record with that key if it does not already exist. This is obviously not the case here, but I expect the Dictionary contract to intentionally be simple in that all operations on the object are divided into “mutating” and “not mutating”, and all “mutating” operations invalidate counters, even if they are do not change anything.

+4
Oct 13 '09 at 20:32
source share

From the documentation (Dictionary.Item Property):

You can also use the Item property to add new items by setting the value of a key that is not in the dictionary. When you set a property value, if the key is in the dictionary, the value associated with this key is replaced with the assigned value. If the key is not in the dictionary, the key and value are added to the dictionary. In contrast, the Add method does not modify existing elements.

So, as John points out, there is no way for the framework to know that you have not changed the contents of the list, so it assumes that you have one.

+1
Oct 13 '09 at 20:35
source share

For those who are interested in how to get around this problem, here is a modified version of Vitaly's code that works:

 class Program { static void Main(string[] args) { var dictionary = new Dictionary<string, int>() { {"1", 1}, {"2", 2}, {"3", 3} }; string[] keyArray = new string[dictionary.Keys.Count]; dictionary.Keys.CopyTo(keyArray, 0); foreach (var s in keyArray) { dictionary[s] = 1; } } } 

The answer is to copy the keys to another enumerated one, and then iterate over this collection. For some reason, there is no KeyCollection.ToList method to simplify the task. Instead, you need to use the KeyCollection.CopyTo method, which copies the keys into an array.

+1
Mar 25 '13 at 23:35
source share

The short answer is that you are changing the collection of dictionaries, even if you are not actually changing any of its keys. Thus, the next iteration, which accesses the collection after your update, throws an exception indicating that the collection has been modified since the last access (and rightly so).

To do what you want, you need another way to iterate through the elements, so changing them will not throw an iterator exception.

0
Oct 13 '09 at 20:58
source share

This is because they designed .NET with the ability to iterate the collection in multiple threads. Thus, you must either allow the iterator to multithread, or prevent it, and also allow the collection to be modified during the iteration, which would require restricting the object that will be displayed in one stream. It cannot be both.

In fact, the answer to your question is that the code you entered actually leads to the creation of a state machine generated by the [CompilerGenerated], which allows iterators to maintain the collection state in order to provide profitability magic. That’s why if you don’t sync your collections and you repeat in one thread and manipulate in another thread, you will get some funky shit.

Check out: http://csharpindepth.com/articles/chapter6/iteratorblockimplementation.aspx

Also: http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ConcurrentHashMap.html "iterators are designed to use only one thread at a time."

0
Jun 26 '14 at 18:53
source share



All Articles