Существуют ли коллекции С#, где модификация не отменяет итераторов?

Существуют ли какие-либо структуры данных в библиотеке С# Collections, где модификация структуры не отменяет итераторов?

Рассмотрим следующее:

List<int> myList = new List<int>();
myList.Add( 1 );
myList.Add( 2 );
List<int>.Enumerator myIter = myList.GetEnumerator();
myIter.MoveNext();  // myIter.Current == 1
myList.Add( 3 );
myIter.MoveNext();  // throws InvalidOperationException

Ответ 1

Да, взгляните на пространство имен System.Collections.Concurrent в .NET 4.0.

Обратите внимание, что для некоторых наборов в этом пространстве имен (например, ConcurrentQueue<T>) это работает, только выставляя перечислитель на "снимок" рассматриваемой коллекции.

Из документация MSDN на ConcurrentQueue<T>:

Перечисление представляет собой моментальный снимок содержимое очереди. Это не отражать любые обновления коллекции после вызова GetEnumerator. перечислитель безопасен для использования одновременно с чтением и записью на очереди.

Однако это не относится ко всем коллекциям. ConcurrentDictionary<TKey, TValue>, например, дает вам счетчик, который поддерживает обновления базовой коллекции между вызовами MoveNext.

Из документация MSDN на ConcurrentDictionary<TKey, TValue>:

Перечислитель вернулся из словарь безопасен для использования одновременно с чтением и записью в словарь, однако он не представляют моментальный снимок времени словарь. Содержимое через счетчик может содержать изменения, внесенные в словарь после вызова GetEnumerator.

Если у вас нет 4.0, я думаю, что остальные права, и нет такой коллекции, предоставляемой .NET. Однако вы всегда можете создавать свои собственные, делая то же самое ConcurrentQueue<T> делает (перебирает снимок).

Ответ 2

В соответствии с этой статьей MSDN на IEnumerator найденное поведение недействительности требуется для всех реализаций IEnumerable.

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

Ответ 3

Поддержка такого поведения требует некоторой довольно сложной внутренней обработки, поэтому большинство коллекций не поддерживают это (я не уверен в пространстве имен Concurrent).

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

Вы можете легко реализовать такую ​​коллекцию, или можете использовать FSharpList<T> из FSharp.Core.dll (но не стандартную часть .NET 4.0):

open Microsoft.FSharp.Collections;

// Create immutable list from other collection
var list = ListModule.OfSeq(anyCollection);
// now we can use `GetEnumerable`
var en = list.GetEnumerable();

// To modify the collection, you create a new collection that adds 
// element to the front (without actually copying everything)
var added = new FSharpList<int>(42, list);

Преимущество неизменных коллекций заключается в том, что вы можете работать с ними (создавая копии), не влияя на исходный, и поэтому поведение, которое вы хотели, "бесплатно". Для получения дополнительной информации, есть великая серия Эрика Липперта.

Ответ 4

Единственный способ сделать это - сделать копию списка перед его повторением:

var myIter = new List<int>(myList).GetEnumerator();

Ответ 5

Нет, их не существует. Стандартные коллекции ALl С# аннулируют числитель при изменении структуры.

Ответ 6

Используйте цикл for вместо foreach, а затем вы можете его изменить. Я бы не советовал, хотя....