0

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?

Victor Sotnikov
  • 177
  • 1
  • 2
  • 15

2 Answers2

0

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.)

Jon S
  • 158
  • 9
  • As I understand, after I change "ToArray() to .ToList()", the error should become "stably reproducible"; that's the essence of you suggestion? – Victor Sotnikov Apr 09 '19 at 16:10
  • Right. If you're making changes to the IEnumerable, ToList'll let you know. That should help you get to the core of the problem. – Jon S Apr 09 '19 at 19:08
  • But in this case some other thread should be involved - a thread that "making changes to the IEnumerable", agree? Who else can "make changes to the IEnumerable" while my thread is calling the .ToArray() ? – Victor Sotnikov Apr 10 '19 at 10:28
  • I don’t know your code, so I can’t answer that question. But one of the best troubleshooting steps I know is to check my assumptions. You might not think that any code is modifying your Enumerable but, if that was true, you wouldn’t be getting the error. – Jon S Apr 10 '19 at 23:41
0

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);
 }
Alex M
  • 141
  • 2
  • 6
  • "you can't modify a collection while using foreach to iterate through it. " - correct; but my code does NOT modify the collection, this is the problem. My code above just enumerates the collection 'instances.WorkContributors' (using .Where, i.e. using filtering); and then converts the result into array. – Victor Sotnikov Apr 09 '19 at 16:42
  • I see, can you please format your code like the one in your "theoretical example" :) – Alex M Apr 09 '19 at 16:54
  • Where iterates through a collection by using ForEach I believe – Alex M Apr 09 '19 at 17:01