Коллекция была изменена; операция перечисления может не выполняться

Этот вопрос задают много раз на этом форуме. Я знаю решение проблемы. Но мне любопытно узнать, почему "Операция перечисления не может выполняться при изменении коллекции"

        List<string> list = new List<string>();

        list.Add("a");

        list.Add("b");

        int[] array = new int[6] { 1, 2, 3, 4, 5, 5 };

        HashSet<int> hashSet = new HashSet<int>();

        int i = 0;

        foreach (string s in list)
        {
            list[i] = "test";

            i++;
        }

Но когда я меняю список на list.toarray Оно работает.

3 ответа

Решение

Microsoft указывает, что любой объект, который реализует iEnumerable, должен аннулировать любые существующие перечисления при изменении объекта. Общая причина этого требования заключается в том, что для многих типов коллекций трудно гарантировать, что перечислитель будет вести себя разумно при изменении коллекции. Например, предположим, что List содержит пять значений (A,B,C,D,E), а перечислитель работает, устанавливая n в число элементов, а затем выводя элемент (0), элемент (1) и т. Д. До элемент (п-1). Если в то время как перечислитель перечисляет элемент (2) [C], элемент BB вставляется после элемента (1) [то есть B], перечислитель может затем перейти к выходному элементу (3) [который снова является C,], тогда элемент 4 [D], а затем решите, что это сделано, поскольку он выводит все пять элементов. Это была бы плохая ситуация (один элемент появился дважды, а другой пропал без вести).

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

  1. Любой элемент, который существует в течение всего периода перечисления, должен быть возвращен ровно один раз.
  2. Любой элемент, который существует в течение части времени перечисления, должен быть возвращен ровно один раз или не возвращаться вообще, но нет никаких требований в отношении того, какие такие элементы возвращаются (если таковые имеются).
  3. Последовательность, в которой возвращаются элементы, должна быть допустимой последовательностью для перечислителя (например, SortedList должен возвращать элементы в отсортированной последовательности; если элемент добавляется в список, который предшествует уже выведенному элементу, этот элемент не должен перечисляться).
  4. Для целей (1) и (2) удаленный элемент считается отличным от добавляемого, даже если ключи идентичны; если ключ элемента изменяется, элемент до изменения считается отличным от элемента после.
  5. Повторные перечисления через Reset не гарантируют возвращение одинаковых элементов.

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

В целом коллекции.Net не поддерживают одновременное перечисление и изменение. Линия list[i] = "test" изменяет коллекцию list пока вы находитесь в середине его перечисления и, следовательно, и генерируется исключение. Правда, это тривиальная модификация и не влияет на структуру списка, но List<T> относится даже к модификациям места как к разрушительным.

ToArray версия работает, потому что теперь у вас есть 2 коллекции

  1. list
  2. Созданный массив

Вы на самом деле затем перечислите массив и, следовательно, измените оригинал list просто отлично.

Когда вы делаете list.ToArray() в вашем foreach вы делаете полную копию содержимого списка и перечисляете ее. Вы больше не вносите изменения в список при перечислении по списку.

Делая это:

foreach (string s in list.ToArray())
{
   list[i] = "test";
   i++;
}

фактически так же, как это:

string[] newArray = list.ToArray();
foreach (string s in newArray)
{
   list[i] = "test";
   i++;
}

Обратите внимание, что во втором случае вы не перечисляете коллекцию, которую вы изменяете, а скорее совершенно новую коллекцию.

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