Reputation: 305
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__97
1.MoveNext()
at System.Linq.Enumerable.WhereEnumerableIterator1.MoveNext()
at System.Linq.Buffer
1..ctor(IEnumerable1 source)
at System.Linq.Enumerable.ToArray[TSource](IEnumerable
1 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
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
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+SZGenericArrayEnumerator
1[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.List
1+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