Невозможно добавить / удалить элементы из коллекции, пока foreach выполняет итерацию по ней - PullRequest
4 голосов
/ 15 июня 2010

Если я создаю собственную реализацию интерфейса IEnumerator, то я могу (внутри оператора foreach) добавлять или удалять элементы из albumsList без генерации исключения. Но если оператор foreach использует IEnumerator предоставляется albumsList, затем попытка добавить / удалить (внутри foreach) элементы из albumsList приведет к исключению:

class Program
{
    static void Main(string[] args)
    {

        string[] rockAlbums = { "rock", "roll", "rain dogs" };
        ArrayList albumsList = new ArrayList(rockAlbums);
        AlbumsCollection ac = new AlbumsCollection(albumsList);
        foreach (string item in ac)
        {
            Console.WriteLine(item);
            albumsList.Remove(item);  //works

        }

        foreach (string item in albumsList)
        {
            albumsList.Remove(item); //exception
        }



    }

    class MyEnumerator : IEnumerator
    {
        ArrayList table;
        int _current = -1;

        public Object Current
        {
            get
            {
                return table[_current];
            }
        }

        public bool MoveNext()
        {
            if (_current + 1 < table.Count)
            {
                _current++;
                return true;
            }
            else
                return false;
        }

        public void Reset()
        {
            _current = -1;
        }

        public MyEnumerator(ArrayList albums)
        {
            this.table = albums;
        }

    }

    class AlbumsCollection : IEnumerable
    {
        public ArrayList albums;

        public IEnumerator GetEnumerator()
        {
            return new MyEnumerator(this.albums);
        }

        public AlbumsCollection(ArrayList albums)
        {
            this.albums = albums;
        }
    }

}

a) Я предполагаю, что код вызывает исключение (при использовании IEnumerator реализация A предоставляется albumsList) находится внутри A?

b) Если я хочу иметь возможность добавлять / удалять элементы из коллекции (пока foreach выполняет итерацию по ней), мне всегда нужно будет предоставлять собственную реализацию интерфейса IEnumerator, или я могуГотовы ли альбомы для добавления / удаления элементов?

спасибо

Ответы [ 4 ]

15 голосов
/ 15 июня 2010

Самый простой способ - либо перевернуть элементы типа for(int i = items.Count-1; i >=0; i--), либо выполнить один цикл, собрать все элементы для удаления в списке, а затем выполнить цикл по элементам для удаления, удалив их из исходного списка.

13 голосов
/ 15 июня 2010

Обычно не рекомендуется разрабатывать классы коллекций, которые позволяют вам изменять коллекцию при перечислении, если только вы не собираетесь разрабатывать что-то поточно-ориентированное, чтобы это было возможно (например, добавление из одного потока при перечислении из другого). * * 1001

Причины несметные. Вот один.

Ваш класс MyEnumerator работает путем увеличения внутреннего счетчика. Его свойство Current предоставляет значение по данному индексу в ArrayList. Это означает, что перечисление в коллекции и удаление «каждого» элемента на самом деле не будут работать должным образом (т. Е. Не удалит каждый элемент в списке).

Рассмотрим эту возможность:

Код, который вы разместили, фактически сделает это:

  1. Вы начинаете с увеличения вашего индекса до 0, что дает вам Current "камня". Вы удаляете «камень».
  2. Теперь у коллекции есть ["roll", "rain dogs"], и вы увеличиваете свой индекс до 1, делая Current равным "дождевым собакам" (НЕ "бросать") . Далее вы удаляете «дождевых псов».
  3. Теперь коллекция имеет ["roll"], и вы увеличиваете свой индекс до 2 (то есть> Count); поэтому ваш счетчик думает, что он закончен.

Есть и другие причины, по которым это проблематичная реализация. Например, кто-то, использующий ваш код, может не понимать, как работает ваш перечислитель (ни не должен они - реализация не должна иметь значения), и поэтому не понимает, что стоимость вызова Remove в пределах foreach За каждую итерацию за блок взимается штраф IndexOf, т. е. линейный поиск (см. документацию MSDN по ArrayList.Remove, чтобы проверить это).

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

ОК, так какова альтернатива? Вот несколько моментов, с которых можно начать:

  1. Не создавайте свою коллекцию так, чтобы она позволяла - не говоря уже о ожидать - изменений в перечислении. Это приводит к любопытному поведению, такому как пример, который я привел выше.
  2. Вместо этого, если вы хотите предоставить возможности массового удаления, рассмотрите такие методы, как Clear (для удаления всех элементов) или RemoveAll (для удаления элементов, соответствующих указанному фильтру).
  3. Эти методы массового удаления могут быть реализованы довольно легко. ArrayList уже имеет метод Clear, как и большинство классов коллекций, которые вы можете использовать в .NET. В противном случае, если ваша внутренняя коллекция проиндексирована, распространенным методом удаления нескольких элементов является перечисление из верхнего индекса с использованием цикла for и вызов RemoveAt для индексов, для которых требуется удаление (обратите внимание, что это устраняет сразу две проблемы: возвращаясь назад сверху, вы гарантируете доступ к каждому элементу в коллекции, более того, используя RemoveAt вместо Remove, вы избегаете штрафа за повторный линейный поиск).
  4. В качестве дополнительного примечания я настоятельно рекомендую для начала избегать неуниверсальных коллекций, таких как ArrayList. Вместо этого используйте жестко типизированные универсальные аналоги, такие как List(Of Album) (при условии, что у вас был Album класс - в противном случае List(Of String) все еще более безопасен, чем ArrayList).
0 голосов
/ 30 декабря 2013

Предположим, у меня есть коллекция, массив в этом отношении

int[] a = { 1, 2, 3, 4, 5 };

У меня есть функция

   public IList<int> myiterator()
        {
            List<int> lst = new List<int>();
            for (int i = 0; i <= 4; i++)
            {
                lst.Add(a[i]);
            }

              return lst;
        }

Теперь я вызываю эту функцию, перебираю и пытаюсь добавить

   var a = myiterator1();
    foreach (var a1 in a)
       {
         a.Add(29);
       }

Вызовет исключение времени выполнения

Здесь следует отметить, что если нам разрешено добавлять для каждого элемента в списке

список станет чем-то вроде {1,2,3,4,5,6} затем для каждого элемента и каждого вновь добавленного мы продолжаем добавлять Coz этого мы застрянем в бесконечной операции, так как она будет повторяться для каждого элемента

0 голосов
/ 15 июня 2010

Из документации MSDN для INotifyCollectionChanged :

Вы можете перечислить любую коллекцию который реализует IEnumerable интерфейс. Однако для настройки динамического привязки, так что вставки или удаления в коллекции обновляют Пользовательский интерфейс автоматически, коллекция должна реализовать INotifyCollectionChanged интерфейс. Этот интерфейс предоставляет Событие CollectionChanged, которое должно быть поднимается всякий раз, когда основной изменения коллекции.

WPF предоставляет ObservableCollection <(Of <(T>)>) класс, который является встроенным осуществление сбора данных что разоблачает Интерфейс INotifyCollectionChanged. Например, см. Как: создать и Привязка к коллекции ObservableCollection.

Отдельные объекты данных в Коллекция должна удовлетворять требования, описанные в Обязательной Обзор источников.

Перед внедрением собственного коллекция, рассмотрите возможность использования ObservableCollection <(Of <(T>)>) или один из существующей коллекции классы, такие как List <(Of <(T>)>), Коллекция <(Of <(T>)>) и BindingList <(Of <(T>)>), среди многих др.

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

Мне кажется, что проблема в самой Коллекции, а не в ее Перечне.

...