Перечисление коллекции
В качестве простого способа перебора элементов коллекции платформа .NET Framework позволяет использовать механизм перечислителей. Перечислители могут лишь читать данные в коллекции; они не могут быть использованы для изменения коллекции, с которой они работают.
В некоторых языках существуют операторы, скрывающие сложности, связанные с непосредственным использованием перечислителей. Перечислители используются оператором языка C# foreach, оператором языка C++ for each и оператором языка Visual Basic For Each.
О перечислителях
Коллекции воспринимаются перечислителями таким образом, что доступ к их элементам может осуществляться последовательно. Различные классы коллекций могут иметь различный порядок следования элементов. Например, перечислитель для класса ArrayList сохраняет порядок, в котором элементы были введены в коллекцию, в то время как перечислитель для класса Hashtable перебирает элементы в соответствии с их хэш-кодом.
Все перечислители основаны на интерфейсе IEnumerator или на универсальном интерфейсе IEnumerator<T>, для чего им необходимо иметь перечисленные ниже члены.
Свойство Current указывает на текущий элемент коллекции.
Свойство MoveNext перемещает перечислитель к следующему элементу коллекции.
Свойство Reset перемещает перечислитель в начало коллекции. Свойство Current при этом указывает на положение перед первым элементом. В универсальном интерфейсе IEnumerator<T> свойство Reset отсутствует.
Поведение перечислителей
Изначально перечислитель располагается перед первым элементом коллекции. Свойство Reset также переводит перечислитель в это положение. В этом положении значение Current не определено. Поэтому до считывания значения свойства Current необходимо вызвать свойство MoveNext, чтобы переместить перечислитель на первый элемент коллекции.
Свойство Current возвращает один и тот же объект до тех пор, пока не вызваны свойства MoveNext или Reset. Свойство MoveNext переводит свойство Current к следующему элементу.
Если при вызове MoveNext перечислитель выходит за границы коллекции, то он помещается после последнего элемента в коллекции, а MoveNext возвращает значение false. Когда перечислитель находится в этой позиции, последующие вызовы MoveNext также возвращают значение false. Если последний вызов MoveNext вернул значение false, то значение Current не определено.
В неуниверсальных коллекциях для перемещения перечислителя обратно в начало можно вызвать Reset, а затем — MoveNext.
В универсальных коллекциях нельзя заново переместить свойство Current на первый элемент коллекции; вместо этого придется создать новый экземпляр перечислителя.
Перечислитель остается допустимым, пока в коллекцию не вносятся изменения. Если в коллекцию вносятся изменения, такие как добавление, изменение или удаление элементов, перечислитель становится необратимо недопустимым, а его поведение — неопределенным.
Перечислитель не имеет монопольного доступа к коллекции, поэтому внутреннее перечисление коллекции в многопоточных операциях не является потокобезопасным. Чтобы гарантировать потокобезопасность при перечислении, можно заблокировать коллекцию на все время выполнения процедуры перечисления. Чтобы разрешить доступ к коллекции для чтения и записи из нескольких потоков, необходимо реализовать собственный механизм синхронизации или использовать один из классов потокобезопасных коллекций в пространстве имен System.Collections.Concurrent. Класс System.Collections.Concurrent.ConcurrentQueue<T> и класс System.Collections.Concurrent.ConcurrentStack<T> для предупреждения внесения изменений коллекций другим потоком создают снимок элементов перед их перечислением. Класс System.Collections.Concurrent.ConcurrentDictionary<TKey, TValue> не выполняет создание снимка.
Класс System.Collections.Concurrent.BlockingCollection<T> предоставляет метод перечислителя с именем GetConsumingEnumerable, который изменяет коллекцию путем удаления элементов из их перечисления.