0

我有一个 .NET 4.0 程序集;它在 GAC 中注册并作为 BizTalk“编排”的一部分工作。有时我会收到以下错误 - “收藏已修改;枚举操作可能不会执行。:System.InvalidOperationException:集合已修改;枚举操作可能无法执行。”。我无法复制它;当我对相同的数据运行相同的处理时,我的程序集不会在这个地方产生错误。

当我为数据表对象调用“.Where().ToArray()”时会发生错误:类 System.Data.TypedTableBase 的对象。

这是代码: .....

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

对象“实例”是一个数据集——我的类是从 System.Data.DataSet 生成的。属性“instances.WorkContributors”是一个数据表:System.Data.TypedTableBase 类的对象。MyDataRowClass 类是从 System.Data.DataRow 生成的。

错误后的调用堆栈如下: Collection was modified; 枚举操作可能不会执行。:System.InvalidOperationException:集合已修改;枚举操作可能不会执行。在 System.Data.RBTree 1.RBTreeEnumerator.MoveNext() at System.Linq.Enumerable.<CastIterator>d__971.MoveNext() 在 System.Linq.Enumerable.WhereEnumerableIterator 1.MoveNext() at System.Linq.Buffer1..ctor(IEnumerable1 source) at System.Linq.Enumerable.ToArray[TSource](IEnumerable1 来源)在 MyProduct.FileParser.Validation.Concreate.PwrTypeValidation.ValidatePublisherNumber() 在 MyProduct.FileParser.Validation.Concreate.PwrTypeValidation.Validate () 在 MyProduct.FileParser.Groups.CWR.NWRGroup.StoreGroup(Int32 workBatchID, CWRFileCommonData commonData) 在 MyProduct.FileParser.CWRParser.ProcessCWRFile(String文件名,布尔等待,布尔删除文件,字符串源文件名)

我不明白为什么会发生错误;以及为什么它只偶尔发生并且不会再次发生在相同的处理数据上。错误“收藏已修改;枚举操作可能无法执行。” 对我来说,这本身就很简单;但我不明白为什么它会发生在我的代码中。如果像这样的代码,则该错误除外:

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

但是我上面的代码只是想枚举 System.Data.TypedTableBase 并将结果转换为数组。

有任何想法吗?

4

2 回答 2

0

使用 foreach 遍历集合时,您不能修改集合。制作副本并从副本中删除。

 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);
 }
于 2019-04-09T16:25:57.167 回答
0

将 .ToArray() 更改为 .ToList()。两者之间存在语义差异,这里的答案最好地说明了这一点

其他几个(好的)答案集中在将发生的微观性能差异上。这篇文章只是补充提及由数组 (T[]) 生成的 IEnumerator 与由 List 返回的IEnumerator 之间存在的语义差异。最好用例子来说明:

 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

这表明 int[] 返回的 IEnumarator 不会跟踪自枚举器创建以来数组是否已被修改。请注意,我将局部变量 source 声明为 IList。这样,我确保 C# 编译器不会将 foreach 语句优化为相当于 for (var idx = 0; idx < source.Length; idx++) { /* ... */ } 循环的东西。如果我使用 var source = ...; 这就是 C# 编译器可能会做的事情 反而。在我当前版本的 .NET 框架中,此处使用的实际枚举器是非公共引用类型 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](在这种情况下装在 IEnumerator 框中,因为我使用 IList)。总之,由 List 生成的枚举器会跟踪列表在枚举期间是否发生变化,而由 T[] 生成的枚举器则不会。因此,在 .ToList() 和 .ToArray() 之间进行选择时,请考虑这种差异。人们经常添加一个额外的 .ToArray() 或 .ToList() 来规避一个跟踪它是否在枚举器的生命周期内被修改的集合。(如果有人想知道 List<> 如何跟踪集合是否被修改,则此类中有一个私有字段 _version ,每次更新 List<> 时都会更改。)

于 2019-04-09T15:39:44.313 回答