My blog about application development on the .NET platform.

Enumerating collections that change in C#

If you try you remove an item from an IEnumerable while enumerating it using a foreach loop in C# you will get an InvalidOperationException saying that “Collection was modified; enumeration operation may not execute”.

As an example, consider the below simple code snippet where I create a List<string> of names and iterate it through and try to remove all names that starts with an ‘A’:

//create a list with some strings
List<string> someNames = new List<string>();
someNames.Add("Bill");
someNames.Add("Mike");
someNames.Add("Alice");
someNames.Add("Trevor");
someNames.Add("Scott");
foreach (string s in someNames) {
  //try to remove all names that start with an 'A'
  if(s.StartsWith("A"))
    someNames.Remove(s); //!!! THIS CODE WON'T WORK AT RUNTIME!!!
}

The compiler won’t complain but running this code will cause an InvalidOperationException to be thrown. If you run the code above using the debugger in Visual Studio you may note that the exception is not thrown on the line where the Remove method is called though.

IEnumerator

This is because it is the enumerator itself – an enumerator is any class that implements the IEnumerator interface – that throws the exception. Why does it do this? Because enumerators are only used to read from a collection, they can’t modify it, and if you modify an item in a collection that is currently being enumerated from some other piece of code it may lead to unexpected and confusing results.

When implementing the IEnumerator interface yourself you should therefore remember to explicitly throw an InvalidOperationException in the MoveNext() method if the collection has changed during the enumeration.

Most of the built-in implementations of the IEnumerable interface in the .NET Framework behave this way. If you for example try to remove a DataRow of a DataTable while iterating over the Rows collection of the DataTable as per the below sample code you will also get an InvalidOperationException despite the fact that the data row is not actually removed from the DataTable until the AcceptChanges() method is called. It is only flagged for deletion (the RowState of the DataRow becomes Deleted) but still the enumerator throws the exception as the state of the collection is modified:

DataTable dt = new DataTable();
//load some data into the DataTable...
foreach (DataRow dr in dt.Rows) {
  //some condition here...
  dr.Delete(); //THIS CODE DOESN'T WORK AT RUNTIME!!!
}

For

What you should do in this cases like this is to simply replace the foreach loop with a for loop and iterate through the collection backwards, i.e. you start from the last index of the collection and then decrement the iterator variable (int i) by 1 in each iteration until you reach the first index (index = 0).

The following code is functionally equivalent to the failing code above but it doesn’t throw any exception:

List<string> someNames = new List<string>();
someNames.Add("Bill");
someNames.Add("Mike");
someNames.Add("Alice");
someNames.Add("Trevor");
someNames.Add("Scott");
for (int i = someNames.Count - 1; i >= 0; i--) {
  if (someNames[i].StartsWith("A"))
    someNames.Remove(someNames[i]);
}

You could also iterate from the first to the last index as usual but then you must remember to decrement the iterator variable whenever you actually do remove an item in the loop. The following code also works as expected:

for (int i = 0; i < someNames.Count; i++) {
  if (someNames[i].StartsWith("A")) {
    someNames.Remove(someNames[i]);
    i--; //decrease the value of the iterator variable
  }
}

Bottom line is that you should never remove an item from or change the state of an IEnumerable that is currently being enumerated using a foreach loop. In these cases you should simply replace the foreach loop with a for loop.

While

The same rule also applies to while loops that are explicitly calling the MoveNext() method of an IEnumerator. The following code will for example also throw an InvalidOperationException for the same reason as the foreach loop above does:

using (IEnumerator<string> enumerator = someNames.GetEnumerator())
{
    bool moveNext = enumerator.MoveNext();
    while (moveNext)
    {
        if (enumerator.Current.StartsWith("A"))
            someNames.Remove(enumerator.Current); //!!! THIS CODE WON'T WORK AT RUNTIME!!!

        moveNext = enumerator.MoveNext();
    }
}

Additional Resources

for (C# Reference): http://msdn.microsoft.com/en-us/library/ch45axte.aspx?f=255&MSPPError=-2147217396
foreach, in (C# Reference): http://msdn.microsoft.com/en-us/library/ttw7t8t6.aspx
IEnumerator Interface: http://msdn.microsoft.com/en-us/library/system.collections.ienumerator(v=vs.110).aspx
while (C# Reference): http://msdn.microsoft.com/en-us/library/2aeyhxcd.aspx


3 Comments on “Enumerating collections that change in C#”

  1. Ankit Rana says:

    Sir but if I use this it work fine.

    foreach (var payRunDetail in payrun.PayRunDetails.ToList())
    {
    payRunDetail.TimeSheetManager.TimeSheetStatus = (int)TimeSheetStatusType.ReadyForInterpretation;
    Context.PayRunDetails.Remove(payRunDetail);
    }

  2. John Karlsson says:

    @Ankit Rana

    You are modifying an object in the collection and not the collection itself. This is allowed.

  3. JasonD says:

    I implemented this using a custom enumerator. Not sure where I got the idea. I have another one for dictionary collections. To call it:

    For Each s As String In New CollectionReverseEnumerator(someNames)
    someNames.Remove(s)
    Next

    Public NotInheritable Class CollectionReverseEnumerator
    Implements IEnumerable
    Implements IEnumerator

    Private Property Position As Int32
    Private Property Source As ICollection

    Public Sub New(ByVal aoList As ICollection)
    Source = aoList
    Position = Source.Count
    End Sub

    Public ReadOnly Property Current() As Object Implements System.Collections.IEnumerator.Current
    Get
    Try
    Return Source(Position)
    Catch ex As IndexOutOfRangeException
    Throw New InvalidOperationException
    End Try
    End Get
    End Property

    Public Function MoveNext() As Boolean Implements System.Collections.IEnumerator.MoveNext
    Position -= 1
    Return (Position > -1)
    End Function

    Public Sub Reset() Implements System.Collections.IEnumerator.Reset
    Position = Source.Count
    End Sub

    Public Function GetEnumerator() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
    Return Me
    End Function
    End Class


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s