Коллекция была изменена; операция перечисления может не выполняться, даже если я не изменяю коллекцию. Я зацикливаюсь [duplicate]

Вы должны различать переопределение и перегрузку. Без ключевого слова virtual вы перегружаете только метод базового класса. Это означает только скрытие. Допустим, у вас есть базовый класс Base и производный класс Specialized, которые оба реализуют void foo(). Теперь у вас есть указатель на Base, указывающий на экземпляр Specialized. Когда вы вызываете foo() на нем, вы можете заметить разницу, которую делает virtual: если метод является виртуальным, будет использоваться реализация Specialized, если он отсутствует, будет выбрана версия из Base. Лучше всего никогда не перегружать методы из базового класса. Создание не виртуального метода - это способ его автора сказать вам, что его расширение в подклассах не предназначено.

12
задан Drew Noakes 28 October 2012 в 20:20
поделиться

7 ответов

Из IEnumerable documentation :

Перечислитель остается в силе, пока коллекция остается неизменной. Если изменения внесены в коллекцию, такие как добавление, изменение или удаление элементов, перечислитель безвозвратно аннулирован и его поведение не определено.

Я считаю, что аргументация для этого решения заключается в том, что он не может быть гарантировано, что все типы коллекций могут поддерживать модификацию и сохранять состояние перечислителя. Рассмотрим связанный список - если вы удалите узел, и в настоящее время на этом узле перечислитель, ссылка на узел может быть его единственным состоянием. И как только этот узел будет удален, ссылка «следующий узел» будет установлена ​​на null, что фактически приведет к аннулированию состояния перечислителя и предотвращению дальнейшего перечисления.

Поскольку некоторые реализации сборников будут иметь серьезные проблемы с такими типами ситуации, было решено сделать эту часть контракта интерфейса IEnumerable. Разрешить модификацию в некоторых ситуациях, а не другие, было бы ужасно запутанным. Кроме того, это будет означать, что существующий код, который может полагаться на модификацию коллекции при ее перечислении, будет иметь серьезные проблемы при изменении реализации коллекции. Поэтому предпочтительным является поведение, совместимое со всеми перечисляемыми.

17
ответ дан cdhowie 25 August 2018 в 17:40
поделиться

Как

Внутри большинство классов базовой коллекции поддерживают номер версии. Всякий раз, когда вы добавляете, удаляете, переупорядочиваете и т. Д. Коллекцию, этот номер версии увеличивается.

Когда вы начинаете перечисление, берется снимок номера версии. Каждый раз вокруг цикла этот номер версии сравнивается с коллекцией, а если они различаются, то это исключение бросается.

Почему

Хотя было бы возможно реализовать IList, чтобы он мог корректно обрабатывать изменения в коллекции, сделанной в , в цикле foreach (с учетом того, что перечислитель отслеживает изменения коллекции), задача гораздо сложнее правильно обрабатывать изменения, внесенные в коллекцию другими потоками во время перечисления. Таким образом, это исключение существует, чтобы помочь выявить уязвимости в вашем коде и обеспечить раннее предупреждение о любых потенциальных неустойчивостях, вызванных манипуляциями с другими потоками.

10
ответ дан Drew Noakes 25 August 2018 в 17:40
поделиться

В то время как другие описали, почему это неверное поведение, они не предложили решение вашей проблемы. Хотя вам может не понадобиться решение, я все равно его предоставит.

Если вы хотите наблюдать коллекцию, которая отличается от коллекции, которую вы повторяете, вы должны вернуть новую коллекцию.

Например ..

IEnumerable<int> sequence = Enumerable.Range(0, 30);
IEnumerable<int> newSequence = new List<int>();

foreach (var item in sequence) {
    if (item < 20) newSequence.Add(item);
}

// now work with newSequence

Вот как вы должны «изменять» коллекции. LINQ использует этот подход, если вы хотите изменить последовательность. Например:

var newSequence = sequence.Where(item => item < 20); // returns new sequence
4
ответ дан Josh Smeaton 25 August 2018 в 17:40
поделиться

Реальная техническая причина в этом сценарии состоит в том, что списки содержат частный член, называемый «версия». Каждая модификация - Добавить / Удалить - увеличивает версию. Перечислитель, возвращающий GetEnumerator, сохраняет версию в момент ее создания и проверяет версию каждый раз, когда вызывается «Далее» - если она не равна, она выдает исключение.

Это верно для встроенного List<T> класс и, возможно, для других коллекций, поэтому, если вы реализуете свой собственный IList (а не просто подклассифицируете / используете встроенную коллекцию внутри), тогда вы сможете обойти это, но, как правило, перечисление и mofication должны выполняться в обратном порядке для -loop или использовать дополнительный список в зависимости от сценария.

Обратите внимание, что изменение элемента отлично, только добавление / удаление не является.

1
ответ дан Michael Stum 25 August 2018 в 17:40
поделиться

Это поведение , указанное :

Перечислители могут использоваться для чтения данных в коллекции, но они не могут использоваться для изменения базовой коллекции.

Это ограничение упрощает внедрение счетчиков. Обратите внимание, что некоторые коллекции (некорректно) позволят вам перечислять при изменении коллекции.

0
ответ дан SLaks 25 August 2018 в 17:40
поделиться

Я предполагаю, что причина в том, что состояние объекта перечислителя связано с состоянием коллекции. Например, перечислитель списка должен иметь int-поле для хранения индекса текущего элемента. Однако, если вы удаляете элемент из списка, вы перемещаете все индексы после того, как элемент останется на одном. В этот момент перечислитель пропускает объект, тем самым проявляя неправильное поведение. Сделать перечислитель действительным для всех возможных случаев потребует сложной логики и может повредить производительность для наиболее распространенного случая (не меняя коллекцию). Я считаю, поэтому дизайнеры коллекций в .NET решили, что они должны просто выбросить исключение, когда перечислитель находится в состоянии invald, а не пытается его исправить.

1
ответ дан Stilgar 25 August 2018 в 17:40
поделиться

Причина, по которой вы видите это: IEnumerator, возвращаемый базовой сборкой, может вывести текущее свойство как доступное только для чтения. Как правило, вы должны избегать изменений в коллекциях (фактически, большинство случаев вы даже не сможете изменить коллекцию), используя для каждого.

0
ответ дан THE DOCTOR 25 August 2018 в 17:40
поделиться
Другие вопросы по тегам:

Похожие вопросы: