Когда элементы изменяются при перечислении, влияет ли это на перечисление? - PullRequest
8 голосов
/ 12 июля 2009

Представьте себе, что во время

foreach(var item in enumerable)

Меняются перечисляемые предметы. Это повлияет на текущий foreach?

Пример:

var enumerable = new List<int>();
enumerable.Add(1);
Parallel.ForEach<int>(enumerable, item =>
{ 
     enumerable.Add(item + 1);
});

Это будет цикл навсегда?

Ответы [ 4 ]

16 голосов
/ 12 июля 2009

Как правило, это должно вызвать исключение. Реализация GetEnumerator () List<T> Предоставляет объект Enumerator<T>, метод MoveNext() которого выглядит следующим образом (из Reflector):

public bool MoveNext()
{
    List<T> list = this.list;
    if ((this.version == list._version) && (this.index < list._size))
    {
        this.current = list._items[this.index];
        this.index++;
        return true;
    }
    return this.MoveNextRare();
}


private bool MoveNextRare()
{
    if (this.version != this.list._version)
    {
        ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
    }
    this.index = this.list._size + 1;
    this.current = default(T);
    return false;
}

list._version изменяется (увеличивается) для каждой операции, которая изменяет Список.

5 голосов
/ 12 июля 2009

Зависит от характера счетчика. Многие из них выдают исключение при изменении коллекции.

Например, List<T> выдает InvalidOperationException, если коллекция изменяется во время перечисления.

1 голос
/ 27 января 2013

Документация Microsoft для IEnumerableIEnumerable<T> - неуниверсальные имена будут относиться к обоим] рекомендует, чтобы каждый раз, когда объект, реализующий эти интерфейсы, изменялся, он должен был сделать недействительными любые экземпляры IEnumerator [IEnumerator<T> ], который он ранее произвел, заставляя их бросать InvalidOperationException при будущих попытках доступа. Хотя ничто в документации Microsoft не зафиксировало каких-либо изменений по сравнению с этой позицией, их фактические реализации IEnumerable, похоже, следуют более слабому правилу, заключающемуся в том, что IEnumerator не должен вести себя бессмысленно, если базовая коллекция изменена; он должен выбросить InvalidOperationException , если он не может вести себя "разумно" . К сожалению, поскольку это правило не указано явно, а скорее вытекает из поведения их классов, неясно, что именно должно означать «разумное» поведение.

Все классы Microsoft, о которых я знаю, будут вызывать исключение при изменении коллекции, если они не могут соответствовать следующим критериям:

  1. Любой элемент, который существует неизменным в течение перечисления, будет возвращен ровно один раз.
  2. Элемент, который добавляется или удаляется во время перечисления, должен возвращаться не более одного раза, но если объект удаляется и повторно добавляется во время перечисления, каждое повторное добавление может рассматриваться как создание нового «элемента».
  3. Если коллекция гарантирует возврат вещей в отсортированной последовательности, эта гарантия должна выполняться, даже если элементы вставлены и удалены [например, если «Фред» добавлен в отсортированный по алфавиту список, а «Джордж» уже был перечислен, «Фред» не должен появляться во время этого перечисления].

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

1 голос
/ 12 июля 2009

Это полностью зависит от того, как реализован IEnumerable.

Со списком будет выдано исключение IllegalOperationException. Но не полагайтесь на это поведение для IEnumarables. Некоторые циклы бесконечны и быстро вызовут исключение OutOfMemory.

...