Когда элементы изменяются во время перечисления, влияет ли это на перечисление?

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

foreach(var item in enumerable)

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

Пример:

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

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


person Jader Dias    schedule 11.07.2009    source источник


Ответы (4)


Как правило, это должно вызывать исключение. Реализация List<T> GetEnumerator() Предоставляет объект 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 изменяется (увеличивается) при каждой операции, которая изменяет список.

person Kenan E. K.    schedule 11.07.2009

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

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

person mmx    schedule 11.07.2009

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

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

  1. Any item which exists unmodified throughout an enumeration will be returned exactly once.
  2. An item which is added or deleted during enumeration shall be returned at most once, but if an object is removed and re-added during enumeration, each re-addition may be regarded as creating a new "item".
  3. If a collection guarantees to return things in a sorted sequence, that guarantee must be met even if items are inserted and removed [e.g. if an "Fred" is added to an alphabetically-sorted list, and "George" has already been enumerated, "Fred" must not appear during that enumeration].

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

person supercat    schedule 27.01.2013

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

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

person Dykam    schedule 11.07.2009
comment
Дыкам: Нет. Это сразу выйдет из строя. - person mmx; 12.07.2009