Dimitar Vouldjeff
Dimitar Vouldjeff

Reputation: 2117

Modifying .NET Dictionary while Enumerating through it

I'm using a Dictionary<long, bool> and I want to change it while I enumerate through it, but it seems this is not allowed. How can I do this?

Upvotes: 19

Views: 10146

Answers (8)

dfluff
dfluff

Reputation: 31

I'm new to c# and still learning this stuff myself, but I came up with a possible alternative solution that I haven't seen offered elsewhere.

I made a new HashSet containing the keys of the dictionary, and then iterated over that to make changes to the dictionary.

Dictionary<int, string> ToBeChanged = new Dictionary<int, string> {
    { 3, "foo" },
    { 9, "bar" },
    { 2, "baz" },
    { 4, "quux" }
};

HashSet<int> KeySet = new HashSet<int>(ToBeChanged.Keys);

foreach( var Key in KeySet )
{
    if( Key == 2 )
    {
        ToBeChanged.Remove(Key);
    }
    else
    {
        ToBeChanged[Key] = "Hello";
    }
}

The above will change every value to "Hello" and will delete the entry with the key of 2, without the need for making a copy of the Dictionary<> and making the changes there followed by copying back, or the need to keep track of what you want to change during the loop and then doing a second loop after the initial iteration to make those changes.

If you're working with a SortedDictionary<> and you care what order you're processing items then copy the keys in to a SortedSet<> instead.

As I say, I'm new to c# so it's very possible that I've overlooked something obvious and there's a reason why I've not seen this approach suggested elsewhere. If that's the case, please enlighten me.

Upvotes: 0

Blue
Blue

Reputation: 22911

As of .net core 3 and higher, this is now possible to Remove items from the dictionary, without invalidating the enumerator (foreach loop):

From the docs here:

.NET Core 3.0+ only: The only mutating methods which do not invalidate enumerators are Remove and Clear.

You should be able to loop through, and remove elements, without causing an error. Creating a list or array as a workaround is no longer needed.

Upvotes: 8

codingBeast
codingBeast

Reputation: 59

Ideally, it is not advisable to do it. The GetEnumerator() that is called while iterating to the next item will throw an InvalidOperationException. However, from .netcore 3.1 onwards, this will work when you do a dictionary.Remove() or dictionary.Clear() from inside the loop.

Here is the documentation.

Upvotes: 5

Prashant Lakhlani
Prashant Lakhlani

Reputation: 5806

You should store the key or object you want to delete and break the loop, and then use the Remove() method to delete the object from the dictionary.

Upvotes: 8

Patrick Beard
Patrick Beard

Reputation: 622

Here's my solution, which depends on System.Linq extensions:

 Dictionary<string, bool> _buttons = new Dictionary<string, bool> {
    { "A Button", false },
    { "B Button", false },
    { "X Button", false },
    { "Y Button", false }
 };
 ...
 foreach (var button in _buttons.ToArray()) {
    var name = button.Key;
    var value = CrossPlatformInputManager.GetButton(name);
    if (value != button.Value) {
        _buttons[name] = value;
    }
 }

It explicitly copies to KeyValuePair[], to avoid the mutation errors.

Upvotes: 0

Bryan Cote-Chang
Bryan Cote-Chang

Reputation: 165

I ran into a similar situation at work. I needed to modify a dictionary while iteratively making it smaller until I didn't have keys/values left.

I started by creating a list of "to be removed" keys, but it was painfully slow with all the other logic that i had to implement. Alternatively, I ended up iterating through the dictionary backwards. Since you are traversing the dictionary from back to front, the iteration won't throw an error.

After I take out my work related logic, it looks silly, but I promise it works and looks something like:

    Dictionary<int, List<int>> dict = new Dictionary<int, List<int>>();
    while(dict.Any())
    {
        foreach(var key in dict.Keys.Reverse())
        {
            if(//conditional)
            {
                dict.Remove(key);
            }
            else
            {
                //do something with dict[key]
            }
        }
    }

Upvotes: -1

Jon Skeet
Jon Skeet

Reputation: 1501586

Don't, basically. It's explicitly not supported. From the docs for Dictionary<,>.GetEnumerator():

An enumerator remains valid as long as the collection remains unchanged. If changes are made to the collection, such as adding, modifying, or deleting elements, the enumerator is irrecoverably invalidated and its behavior is undefined.

Typically the best way is to remember the modifications you want to make, and then perform them afterwards. Or you could take a copy of the dictionary to start with and then iterate through that while you modify the original. If you could give us more information about what you're trying to do, that would help.

Having said this, the new ConcurrentDictionary class in .NET 4.0 does permit this - but the results of the iteration aren't guaranteed - you may see the changes while you're iterating, or you may not.

Upvotes: 11

I answered it here with respect to queue's but the answer is the same. You can't enumerate with foreach and modify the collection at the same time.

Upvotes: 1

Related Questions