Victor Sotnikov
Victor Sotnikov

Reputation: 305

“Collection was modified; enumeration operation might not execute” inside System.Data.TypedTableBase<>

I have an .NET 4.0 assembly; it is registered in GAC and works as part of a BizTalk “orchestration”. Sometimes I get the following error - “Collection was modified; enumeration operation might not execute. : System.InvalidOperationException: Collection was modified; enumeration operation might not execute.”. I cannot reproduce it; when I run the same processing of the same data, my assembly does not generate the error in this place.

The error happens when I call ‘.Where().ToArray()’ for a datatable object: an object of classd System.Data.TypedTableBase.

Here is the code: ..................

int? setTypeGroupId;
...

return instances.WorkContributors.Where
    (
        c =>
            !c.IsInterestedPartyNoNull()
            && c.InterestedPartyNo == publisherIpNo
            && c.SetTypeNo == 1
            && (c.RecordType == "SPU")
        && c.TypeCode == "E" 
            && (!setTypeGroupId.HasValue ||  
            (setTypeGroupId.HasValue && c.SetTypeGroupID == setTypeGroupId))
    ).ToArray();
..................

The object ‘instances’ is a dataset – my class produced from System.Data.DataSet. The property ‘instances.WorkContributors’ is a datatable: an object of class System.Data.TypedTableBase. The class MyDataRowClass is produced from System.Data.DataRow.

The call stack after the error was the following: Collection was modified; enumeration operation might not execute. : System.InvalidOperationException: Collection was modified; enumeration operation might not execute. at System.Data.RBTree1.RBTreeEnumerator.MoveNext() at System.Linq.Enumerable.<CastIterator>d__971.MoveNext() at System.Linq.Enumerable.WhereEnumerableIterator1.MoveNext() at System.Linq.Buffer1..ctor(IEnumerable1 source) at System.Linq.Enumerable.ToArray[TSource](IEnumerable1 source) at MyProduct.FileParser.Types.CWR.PWRType.GetPublishers(CWRWorkInstances instances, Nullable`1 setTypeGroupId) at MyProduct.FileParser.Validation.Concreate.PwrTypeValidation.ValidatePublisherNumber() at MyProduct.FileParser.Validation.Concreate.PwrTypeValidation.Validate() at MyProduct.FileParser.Types.CWR.PWRType.StoreRecord(CWRWorkInstances workInstances, CWRWorkParsingContext context) at MyProduct.FileParser.Groups.CWR.NWRGroup.StoreGroup(Int32 workBatchID, CWRFileCommonData commonData) at MyProduct.FileParser.CWRParser.ProcessCWRFile(String fileName, Boolean wait, Boolean deleteFile, String sourceFileName)

I cannot understand why the error happens; and why it happens only sometimes and does not happen on the same processed data again. The error “Collection was modified; enumeration operation might not execute.” Itself is pretty straightforward for me; but I do not see why it happens in that my code. The error is excepted if a code like this:

foreach (DataRow currRow in _someDataTable.Rows)
{
    if (/*deletion condition*/)
    {
        someDataTable.Rows.Remove(currRow);
    }
}

But my code above just wants to enumerate System.Data.TypedTableBase and convert the result into an array.

Any ideas?

Upvotes: 0

Views: 2679

Answers (2)

Alex M
Alex M

Reputation: 147

You can't modify a collection while using foreach to iterate through it. Make a copy and delete from copy.

 DataTable Junk = new DataTable();
 foreach (DataRow currRow in _someDataTable.Rows)
 {
     if (/*deletion condition*/)
     {
         Junk.Add(currRow);
     }
 }
 foreach (DataRow row in Junk)
 {
    _ someDataTable.Rows.REmove(row);
 }

Upvotes: 0

Jon S
Jon S

Reputation: 168

Change .ToArray() to .ToList(). There's a semantic difference between the two, best illustrated by the answer here

A couple of other (good) answers have concentrated on microscopic performance differences that will occur. This post is just a supplement to mention the semantic difference that exists between the IEnumerator produced by an array (T[]) as compared to that returned by a List. Best illustrated with by example:

 IList<int> source = Enumerable.Range(1, 10).ToArray();  // try changing to .ToList()

 foreach (var x in source) {   if (x == 5)
    source[8] *= 100;   Console.WriteLine(x); } 
The above code will run with no exception and produces the output: 
1 
2 
3 
4 
5 
6 
7 
8 
900 
10

This shows that the IEnumarator returned by an int[] does not keep track on whether the array has been modified since the creation of the enumerator. Note that I declared the local variable source as an IList. In that way I make sure the C# compiler does not optimze the foreach statement into something which is equivalent to a for (var idx = 0; idx < source.Length; idx++) { /* ... */ } loop. This is something the C# compiler might do if I use var source = ...; instead. In my current version of the .NET framework the actual enumerator used here is a non-public reference-type System.SZArrayHelper+SZGenericArrayEnumerator1[System.Int32] but of course this is an implementation detail. Now, if I change .ToArray() into .ToList(), I get only: 1 2 3 4 5 followed by a System.InvalidOperationException blow-up saying: Collection was modified; enumeration operation may not execute. The underlying enumerator in this case is the public mutable value-type System.Collections.Generic.List1+Enumerator[System.Int32] (boxed inside an IEnumerator box in this case because I use IList). In conclusion, the enumerator produced by a List keeps track on whether the list changes during enumeration, while the enumerator produced by T[] does not. So consider this difference when choosing between .ToList() and .ToArray(). People often add one extra .ToArray() or .ToList() to circumvent a collection that keeps track on whether it was modified during the life-time of an enumerator. (If anybody wants to know how the List<> keeps track on whether collection was modified, there is a private field _version in this class which is changed everytime the List<> is updated.)

Upvotes: 0

Related Questions