Существуют ли коллекции C #, в которых модификация не делает итераторов недействительными? - PullRequest
10 голосов
/ 02 мая 2010

Существуют ли какие-либо структуры данных в библиотеке коллекций C #, в которых изменение структуры не делает недействительными итераторы?

Обратите внимание на следующее:

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

Ответы [ 6 ]

11 голосов
/ 02 мая 2010

Да, взгляните на пространство имен 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> (переберите снимок).

8 голосов
/ 02 мая 2010

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

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

5 голосов
/ 02 мая 2010

Для поддержки этого поведения требуется довольно сложная внутренняя обработка, поэтому большинство коллекций не поддерживают это (я не уверен насчет пространства имен 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);

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

1 голос
/ 02 мая 2010

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

var myIter = new List<int>(myList).GetEnumerator();
0 голосов
/ 02 мая 2010

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

0 голосов
/ 02 мая 2010

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

...