"Коллекция была изменена; операция перечисления может не выполняться "внутри System.Data.TypedTableBase<>

У меня есть сборка.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();
..................

Объект 'instances' - это набор данных - мой класс, созданный из System.Data.DataSet. Свойство instances.WorkContributors представляет собой объект данных: объект класса System.Data.TypedTableBase. Класс MyDataRowClass создается из System.Data.DataRow.

Стек вызовов после ошибки был следующим: коллекция была изменена; Операция перечисления может не выполняться.: System.InvalidOperationException: коллекция была изменена; Операция перечисления может не выполняться. на System.Data.RBTree1.RBTreeEnumerator.MoveNext() at System.Linq.Enumerable.<CastIterator>d__971.MoveNext () в System.Linq.Enumerable.WhereEnumerableIterator1.MoveNext() at System.Linq.Buffer1..ctor (IEnumerable1 source) at System.Linq.Enumerable.ToArray[TSource](IEnumerable1 источник) в MyProduct.FileParser.Types.CWR.PWRType.GetPublishers(экземпляры CWRWorkInstances, Nullable`1 setTypeGroupId) в MyProduct.FileParser.Validation.Concreate.PwrTypeValidalValatealValatealValatealValatealValatealValatealVateCateRate.Wid.Wid.Wid.Wid.Wid.Wid.File.Valid.Player.Valid_Vid.Wid_Vid.Player_Valid_Vid_Rate_Vid_Vate_Vid_Vate_Vate_Vate_Vate_Vate_Vate_Vate_Vate_Vid.Wid. () в MyProduct.FileParser.Types.CWR.PWRType.StoreRecord(CWRWorkInstances workInstances, CWRWorkParsingContext) в MyProduct.FileParser.Groups.CWR.NWRGroup. fileName, логическое ожидание, логическое deleteFile, String sourceFileName)

Я не могу понять, почему происходит ошибка; и почему это происходит только иногда и больше не происходит с теми же обработанными данными. Ошибка "Коллекция была изменена; операция перечисления может не выполняться ". Само по себе довольно просто; но я не понимаю, почему так происходит в моем коде. Ошибка исключается, если код, подобный этому:

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

Но мой код выше просто хочет перечислить System.Data.TypedTableBase и преобразовать результат в массив.

Есть идеи?

2 ответа

Вы не можете изменить коллекцию, используя 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);
 }

Измените.ToArray() на.ToList(). Между этими двумя понятиями есть смысловая разница, лучше всего иллюстрируемая ответом здесь.

Несколько других (хороших) ответов были сосредоточены на микроскопических различиях в производительности. Этот пост является просто дополнением, чтобы упомянуть семантическое различие, которое существует между IEnumerator, созданным массивом (T[]), по сравнению с тем, который возвращается List. Лучше всего иллюстрируется на примере:

 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

Это показывает, что IEnumarator, возвращаемый int[], не отслеживает, был ли массив изменен с момента создания перечислителя. Обратите внимание, что я объявил локальную переменную source как IList. Таким образом, я проверяю, что компилятор C# не оптимизирует оператор foreach в нечто, эквивалентное циклу for (var idx = 0; idx 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] (в данном случае заключенный в коробку IEnumerator, потому что я использую IList). В заключение, перечислитель, созданный списком, отслеживает, меняется ли список во время перечисления, а перечислитель, созданный T [], - нет. Поэтому учитывайте эту разницу при выборе между.ToList () и.ToArray(). Люди часто добавляют один дополнительный.ToArray() или.ToList (), чтобы обойти коллекцию, которая отслеживает, была ли она изменена во время жизни перечислителя. (Если кто-то хочет знать, как List<> отслеживает, была ли изменена коллекция, в этом классе есть закрытое поле _version, которое изменяется при каждом обновлении List<>.)

Другие вопросы по тегам