jump to navigation

Collection was modified; enumeration operation may not execute February 20, 2008

Posted by frater in All, Software Development.
trackback

This was a weird error I came across recently with a very simple solution, that came about because I tried to do something that really I should have known I couldn’t do. Often the way that the most irritating problems look incredibly simple in hindsight and you wonder how you could have been inattentive enough to fall into them.

Personally, i’m blaming fatigue, but I always use that excuse.

This error message, “Collection was modified; enumeration operation may not execute”, comes about in foreach loops.  Generally it occurs if you try and modify a collection’s references while using foreach to enumerate through it.

An example:

// Assume this value has been populated with a series of true and false values
List<bool> booleanValues;

// Attempting to remove all false’s from the list
foreach(bool b in booleanValues)
{
if (!b)
booleanValues.remove(b);
}

This code should generate the error, as it modifies the collection’s references whilst looping, thus disturbing the progression of the enumerator.

The simplest way around this involves using two loops, one to find the necessary values and the other to remove them, as shown.

List<bool> booleanValues;
List<bool> valuesToDelete = new List<bool>();

foreach(bool b in booleanValues)
{
if (!b)
valuesToDelete.add(b);

}

foreach(bool b in valuesToDelete)
{
booleanValues.remove(b);
}

Edit:

If you’re interested in the performance discussion that took place in the comments below, some more good testing and discussion on this was done at Schnieds blog:

http://www.schnieds.com/2009/03/linq-vs-foreach-vs-for-loop-performance.html

Comments»

1. Dillip Kumar Behera - May 23, 2008

This is Really Good Message & Helpful.

2. dotnetspider - August 5, 2008

generic list removal is appalling!!!

3. Chris Hsy - August 19, 2008

Perfect answer for my confusion, Thank you!!

4. frater - September 4, 2008

Glad I could be of help.

dotnetspider – not really sure what you mean?

5. Michael - January 14, 2009

Or better to use a for loop.

for (int i = 0; i <= booleanValues.Count – 1;i++)
{
if (!booleanValues[i])
booleanValues.RemoveAt(i);
}

frater - January 15, 2009

Not sure that I would call that ‘better’ for two main reasons Michael.

Firstly, you’re calling count on every iteration, which on a large loop will eat up a large amount of processing cycles. Calling “count” on an object is a far more expensive operation than just checking a variable, or iterating through a list.

Also, i’m not sure that it would work. Assume for a moment that i is 10. You remove the the object at i (index 10) and the list reshuffles so now the next object is at object 10. You then move on to check object 11 and completely skip the processing of the object that was next in line after the deleted one.

I havn’t got my documentation here so I cannot check the functionality of RemoveAt, but that would definately be my guess as to what would happen. The real point of this posting is to show that -any- deletion of objects while iterating through the list like that is fundamentally a bad idea.

6. Michael - January 16, 2009

Sorry yes, you need to decrease i by 1 when an item is removed from the list to avoid skipping the next value.

for (int i = 0; i <= booleanValues.Count – 1; i++)
{
if (!booleanValues[i])
{
booleanValues.RemoveAt(i);
i–;
}
}

One this is in place however the RemoveAt() method is much faster.
Start a new a new console application and try this

My code took about 10 seconds and yours took nearly a minute and a half.

using System;
using System.Collections.Generic;

namespace LoopTest
{
internal class Program
{
private static void Main(string[] args)
{
DateTime startTime;
List booleanValues;

booleanValues = GetBooleanList();

startTime = DateTime.Now;
for (int i = 0; i <= booleanValues.Count – 1; i++)
{
if (!booleanValues[i])
{
booleanValues.RemoveAt(i);
i–;
}
}

Console.WriteLine(string.Format("Bool Count = {0}, Time = {1}", booleanValues.Count,
DateTime.Now.Subtract(startTime)));
booleanValues = GetBooleanList();

startTime = DateTime.Now;
List valuesToDelete = new List();
foreach (bool b in booleanValues)
{
if (!b)
valuesToDelete.Add(b);
}
foreach (bool b in valuesToDelete)
{
booleanValues.Remove(b);
}

Console.WriteLine(string.Format(“Bool Count = {0}, Time = {1}”, booleanValues.Count,
DateTime.Now.Subtract(startTime)));
Console.Write(“\nPress any key to continue:”);
Console.Read();
}

private static List GetBooleanList()
{
List values = new List();
for (int i = 0; i <= 100000; i++)
{
values.Add(true);
}
for (int i = 0; i <= 100000; i++)
{
values.Add(false);
}
for (int i = 0; i <= 100000; i++)
{
values.Add(true);
}
for (int i = 0; i <= 100000; i++)
{
values.Add(false);
}
return values;
}
}
}

7. Michael - January 20, 2009

You can always cache the result of (booleanValues.Count – 1) and decrement though I did not notice a performance improvement by doing this.

8. Bernard Vander Beken - January 23, 2009

If you change the line
foreach(bool b in booleanValues)
to
foreach(bool b in booleanValues.ToArray())

then you should be able to remove the values from the list in this foreach loop (thanks to stackoverflow.com for this hint).

9. lakshmi - February 10, 2009

Thanks,
This is What I was looking for.

10. Tom - February 12, 2009

Thanks Frater. I had the same problem and the explanation you did is great. But since I only need to delete one from the collection, using foreach loop will work. Just call the “break” statement after calling the Remove method. This will stop the iteration of the collection and error will not appear anymore. Thanks again.

foreach (string s in PacketList)
{
if (s.IndexOf(sTag) == 1)
{
PacketList.Remove(s);
break;// without this the exception will be thrown
}
}

11. Michael - February 20, 2009

“Calling “count” on an object is a far more expensive operation than just checking a variable, or iterating through a list.”

is not true because upon closer inspection of the disassembly of List I noticed Count is simply a property with backing field that is evaulated in Add(), Remove(), RemoveAt() etc.

public int Count
{
get
{
return this._size;
}
}

12. baby - February 25, 2009

While I’m working in calenders dayrender event, in dynamically creating a linkbutton this error occurs.

if (e.Day.Date.Month == 1 && e.Day.Date.Day == 26)
{
LinkButton Link = new LinkButton();
Link.Text = “Event”;
Link.ID = “LinkButton1″;
Link.Click += new EventHandler(LinkButton1_Click);
}

13. luke - April 28, 2009

Thanks mate, you made that really clear.

14. TEST - July 15, 2009

The Data Access Application Block component includes the Microsoft Visual Basic .NET source code or the Microsoft Visual Basic 2005 source code and the Microsoft Visual C# .NET source code or the Microsoft Visual C# 2005 source code for the Data Access Application Block. It also includes a Quick Start Samples client application in each language that you can use to test common scenarios. The sample can help you to better understand how the Data Access Application Block works. You can also customize the source code to fit your requirements.

15. Bhavin - October 8, 2009

This is really helpful in my case. Thanks a lot for writing such a nice article.