Существуют ли коллекции С#, где модификация не отменяет итераторов?
Существуют ли какие-либо структуры данных в библиотеке С# 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, а затем вы можете его изменить. Я бы не советовал, хотя....