34
foreach(BruteforceEntry be in Entries.Values)
{
    if (be.AddedTimeRemove <= now)
        Entries.Remove(be.IPAddress);
    else if (be.Unbantime <= now && be.Unbantime.Day == DateTime.Now.Day)
        Entries.Remove(be.IPAddress);
}

An exception was thrown:

Collection was modified; enumeration operation may not execute.

For some reason, it is not any more.

I know you cannot remove something, while iterating through it this way. My question is: How do I solve it?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Basser
  • 581
  • 2
  • 5
  • 14
  • Related: [Best way to remove multiple items matching a predicate from a .NET Dictionary?](https://stackoverflow.com/questions/469202/best-way-to-remove-multiple-items-matching-a-predicate-from-a-net-dictionary) – Theodor Zoulias May 28 '22 at 11:17

8 Answers8

50

You can't modify a collection you're iterating over. In this case, a much better solution would be to create a list of entries to remove by iterating over the dictionary, and then iterate over that list, removing the entries from the dictionary:

List<string> removals = new List<string>();                    
DateTime now = DateTime.Now;
foreach(BruteforceEntry be in Entries.Values)
{
    if (be.AddedTimeRemove <= now ||
        (be.Unbantime <= now && be.Unbantime.Day == DateTime.Now.Day))
    {
        removals.Add(be.IPAddress);
    }
}
foreach (string address in removals)
{
    Entries.Remove(address);
}

Note that if you're using .NET 3.5, you could use a LINQ query to express the first part:

List<string> removals = (from be in Entries.Values
                         where be.AddedTimeRemove <= now ||
                               (be.Unbantime <= now && 
                                be.Unbantime.Day == DateTime.Now.Day)
                         select be.IPAddress).ToList();
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 2
    Thanks a lot. I have 1 question, do you like this more than simly iterating through a copy, using .ToList() since that is a smaller edit. – Basser Aug 17 '10 at 19:36
  • 2
    @Basser: Well, for one thing this is likely to have a smaller memory footprint. It's also only iterating over the whole collection once - your current solution iterates once to build up the list, and then it iterates over that list. Don't get me wrong - it'll work... I just prefer this approach. – Jon Skeet Aug 17 '10 at 19:38
  • well I just got told a top SO user answered my question, that's why I thought I'd ask you! I guess I'll use this method, as you are correct! Thanks again. I'm not too good with collections yet, still learning. – Basser Aug 17 '10 at 19:41
  • 1
    Alternately, you can copy items you don't want to remove to a new Dictionary; only 1 iteration over the collection. – Granger Jun 04 '14 at 15:00
  • 1
    @Granger: Right - it depends on whether you have other pieces of code which have a reference to the dictionary and you *want* them to see the changes in the existing collection. – Jon Skeet Jun 04 '14 at 15:15
  • I think, adding a `.ToList()` to the iterated collection and thus creating a new collection that will be iterated is the easier way to do what the OP wanted - see [@digEmAll's answer](https://stackoverflow.com/a/3506181/177710). – Oliver Sep 19 '17 at 08:34
  • @Oliver: That ends up making a copy of much more than is required... and it still combines the "decision" part and the "action" part. I personally think that separating "what entries am I interested in" and "what do I want to do with those entries" leads to clearer code as well as more efficient code. I'm not going to change this answer, but digEmAll's answer is obviously available for those who prefer that approach. – Jon Skeet Sep 19 '17 at 08:37
  • @JonSkeet: Thanks for your insight. Afaik, we're only copying references and I would argue that usually people work on rather smallish collection of less that a hundred or even a thousand items. As long as the code path is not critical, I would not bother with the extra lines of code. But that's surely a matter of taste :-) – Oliver Sep 19 '17 at 08:40
12

Simply put: you can't remove an entry from a collection while you are iterating over it.

One possible workaround is to create a shallow copy of the collection (e.g using ToList) and iterate over that :

foreach(BruteforceEntry be in Entries.Values.ToList())
{
    // modify the original collection
}
digEmAll
  • 56,430
  • 9
  • 115
  • 140
  • This won't work because you are still getting an iterator from the List<>. It's the iterator, not the collection, that is causing the problem. – Dave White Aug 17 '10 at 19:33
  • My solution was only an example of workaround. In terms of performance, and clarity I suggest you to adopt Jon's solution ;) – digEmAll Aug 17 '10 at 19:35
  • 9
    @Dave, the iterator is created from the ToList List instance, the modification is against Entries. This works. – Marc Aug 17 '10 at 19:36
3

This (in my opinion) is the easiest way:

Dictionary<String, String> A = new Dictionary<string, string>(); //Example Dictionary
A.Add("A", "A"); //Example Values
A.Add("B", "B");
A.Add("C", "C");

for (int i = A.Count - 1; i >= 0; i--) //Loop backwards so you can remove elements.
{
     KeyValuePair<String, String> KeyValue = A.ElementAt(i); //Get current Element.
     if (KeyValue.Value == "B") A.Remove(KeyValue.Key);
}

In your case:

for (int i = Entries.Count - 1; i >= 0; i--)
{
    KeyValuePair<String, BruteforceEntry> KeyValue = Entries.ElementAt(i);
    if (KeyValue.Value.AddedTimeRemove <= now)
         Entries.Remove(KeyValue.Key);
    else if (KeyValue.Value.Unbantime <= now && KeyValue.Value.Unbantime.Day == DateTime.Now.Day)
         Entries.Remove(KeyValue.Key);
}
Blam
  • 2,888
  • 3
  • 25
  • 39
  • 1
    -1 While this may work for some implementations of .NET, the `Dictionary` class does not **guarantee** that the order of its elements (when iterated over as an `IEnumerable>`) is unchanged when the dictionary is modified. – Ergwun Jun 18 '13 at 06:43
  • 1
    From http://msdn.microsoft.com/en-us/library/xfhwa508.aspx: "For purposes of enumeration, each item in the dictionary is treated as a KeyValuePair structure representing a value and its key. The order in which the items are returned is undefined." – Ergwun Jun 18 '13 at 06:46
  • @Ergwun - but this solution is not using `IEnumerable` to iterate through the dictionary. `.ElementAt` returns a key-value pair at a specific index – Matt Wilko Jul 26 '13 at 09:56
  • 1
    @MattWilko - `.ElementAt` is an extension method that finds an element by iterating over the dictionary using `IEnumerable`: http://msdn.microsoft.com/en-us/library/bb299233.aspx – Ergwun Jul 27 '13 at 06:01
  • @Ergwun - but the difference is that it doesn't remove the dictionary pair while it is still iterating through the collection; it finds the last element and (conditionally) removes it, then finds the next last element and removes it. – Matt Wilko Jul 29 '13 at 07:46
  • @MattWilko - but if order is not guaranteed to be preserved after a removal, then a pair that haven't been checked yet may jump to a high index and will never be checked. – Ergwun Jul 30 '13 at 14:16
  • .ElementAt is too slow. Overall complexity is O(N^2). – Gqqnbig Sep 09 '15 at 20:12
2

You can change foreach(BruteforceEntry be in Entries.Values) to foreach(BruteforceEntry be in new List<BruteforceEntry>(Entries.Values))

That way you don't modify your collection, but rather a copy of it.

Albin Sunnanbo
  • 46,430
  • 8
  • 69
  • 108
  • This won't work because you are still getting an iterator from the List<>. It's the iterator, not the collection, that is causing the problem. – Dave White Aug 17 '10 at 19:31
  • 1
    It sure works, you are not removing from the List<> iterator, you are removing from the Dictionary<> iterator. The List<> enumerates the complete Dictionary<> iterator before the first remove from the dictionary. – Albin Sunnanbo Aug 17 '10 at 19:37
2

It looks like your question is about why an exception is not being thrown where it used to be. As the other answers state, you generally cannot change a collection through which you are iterating, but you are iterating over the Values collection, which, I believe, is a copy of the values in the dictionary, rather than referring to the main dictionary collection itself. So it no longer has the problem of iterating and modifying the same thing.

BlueMonkMN
  • 25,079
  • 9
  • 80
  • 146
1

You can't modify the collection when iterating over it with foreach. Instead, iterate over it with a for loop.

David
  • 208,112
  • 36
  • 198
  • 279
  • 2
    If your going to be removing items from the collection, be sure to iterate over it in reverse (from length-1 to 0) and not 0 to length-1 – Dave White Aug 17 '10 at 19:28
  • 1
    You better iterate backwards if you're removing items and update your increment variable accordingly if adding. This is prone to mistakes. – Marc Aug 17 '10 at 19:29
  • True, it would make for some slightly uglier code. Just harkening back to my old Perl days, I suppose :) – David Aug 17 '10 at 19:45
0

Boiling down your problem...

foreach(... ... in Entries.Values)
{
        Entries.Remove(...);

}

As the others have said you are modifying the iterator while iterating.

You can, as @David said, use a for loop instead, but make sure to start at the end (reverse iteration).

µBio
  • 10,668
  • 6
  • 38
  • 56
0

I tried another approach that works great for smaller dictionaries.

The idea is to just iterate the dictionary from start until everything is removed. Because the enumerator is a struct, it is allocation free in .NET Core. Of course this method should not be used for large dictionaries.

while (true)
{
    var isRemoved = false;

    foreach (var kvp in dict)
    {
        if (kvp.Key == "15" || kvp.Key == "20")
        {
            source.Remove(kvp.Key);
            isRemoved = true;
            break;
        }
    }

    if (!isRemoved)
    {
        break;
    }
}

I tested a few alternatives:

[SimpleJob]
[MemoryDiagnoser]
public class DictionaryRemove
{
    private Dictionary<string, string> source;

    [IterationSetup]
    public void Setuo()
    {
        source = new Dictionary<string, string>();

        for (var i = 0; i < 20; i++)
        {
            source.Add($"{i}", $"{i}");
        }
    }

    [Benchmark]
    public void Remove_Linq_Keys()
    {
        foreach (var key in source.Keys.Where(x => x == "15" || x == "20").ToList())
        {
            source.Remove(key);
        }
    }

    [Benchmark]
    public void Remove_Linq_Normal()
    {
        foreach (var kvp in source.Where(x => x.Key == "15" || x.Key == "20").ToList())
        {
            source.Remove(kvp.Value);
        }
    }

    [Benchmark]
    public void Remove_Plain()
    {
        while (true)
        {
            var isRemoved = false;

            foreach (var kvp in source)
            {
                if (kvp.Key == "15" || kvp.Key == "20")
                {
                    source.Remove(kvp.Key);
                    isRemoved = true;
                    break;
                }
            }

            if (!isRemoved)
            {
                break;
            }
        }
    }
}

And the results

|             Method |      Mean |     Error |    StdDev |    Median | Allocated |
|------------------- |----------:|----------:|----------:|----------:|----------:|
|   Remove_Linq_Keys | 16.680 us | 1.2932 us | 3.7519 us | 16.200 us |     688 B |
| Remove_Linq_Normal |  6.755 us | 0.4254 us | 1.1928 us |  6.200 us |     720 B |
|       Remove_Plain |  1.936 us | 0.1540 us | 0.4319 us |  1.800 us |     480 B |

If you know that your dictionary is relatively small, it might be a good alternative.

SebastianStehle
  • 2,409
  • 1
  • 22
  • 32