Skip to content

Latest commit

 

History

History
4103 lines (3591 loc) · 338 KB

c25.md

File metadata and controls

4103 lines (3591 loc) · 338 KB

ГЛАВА 25. Коллекции, перечислители и итераторы

В этой главе речь пойдет об одной из самых важных составляющих среды .NET Framework: коллекци­ ях. В С# коллекция представляет собой совокупность объектов. В среде .NET Framework имеется немало интер­ фейсов и классов, в которых определяются и реализуются различные типы коллекций. Коллекции упрощают реше­ ние многих задач программирования благодаря тому, что предлагают готовые решения для создания целого ряда типичных, но порой трудоемких для разработки структур данных. Например, в среду .NET Framework встроены кол­ лекции, предназначенные для поддержки динамических массивов, связных списков, стеков, очередей и хеш-таблиц. Коллекции являются современным технологическим сред­ ством, заслуживающим пристального внимания всех, кто программирует на С#.

Первоначально существовали только классы необоб­ щенных коллекций. Но с внедрением обобщений в версии C# 2.0 среда .NET Framework была дополнена многими новыми обобщенными классами и интерфейсами. Благо­ даря введению обобщенных коллекций общее количество классов и интерфейсов удвоилось. Вместе с библиотекой распараллеливания задач (TPL) в версии 4.0 среды .NET Framework появился ряд новых классов коллекций, пред­ назначенных для применения в тех случаях, когда доступ к коллекции осуществляется из нескольких потоков. Нетруд­ но догадаться, что прикладной интерфейс Collections API составляет значительную часть среды .NET Framework.

Кроме того, в настоящей главе рассматриваются два сред­ ства, непосредственно связанные с коллекциями: перечисли­ тели и итераторы. И те и другие позволяют поочередно обра­ щаться к содержимому класса коллекции в цикле foreach.

Краткий обзор коллекций

Главное преимущество коллекций заключается в том, что они стандартизируют об­ работку групп объектов в программе. Все коллекции разработаны на основе набора четко определенных интерфейсов. Некоторые встроенные реализации таких интер­ фейсов, в том числе ArrayList, Hashtable, Stack и Queue, могут применяться в ис­ ходном виде и без каких-либо изменений. Имеется также возможность реализовать собственную коллекцию, хотя потребность в этом возникает крайне редко.

В среде .NET Framework поддерживаются пять типов коллекций: необобщенные, специальные, с поразрядной организацией, обобщенные и параллельные. Необобщен­ ные коллекции реализуют ряд основных структур данных, включая динамический мас­ сив, стек, очередь, а также словари, в которых можно хранить пары "ключ-значение". В отношении необобщенных коллекций важно иметь в виду следующее: они опериру­ ют данными типа object.

Таким образом, необобщенные коллекции могут служить для хранения данных любого типа, причем в одной коллекции допускается наличие разнотипных данных. Очевидно, что такие коллекции не типизированы, поскольку в них хранятся ссылки на данные типа object. Классы и интерфейсы необобщенных коллекций находятся в пространстве имен System.Collections.

Специальные коллекции оперируют данными конкретного типа или же делают это каким-то особым образом. Например, имеются специальные коллекции для сим­ вольных строк, а также специальные коллекции, в которых используется однонаправ­ ленный список. Специальные коллекции объявляются в пространстве имен System. Collections.Specialized.

В прикладном интерфейсе Collections API определена одна коллекция с поразряд­ ной организацией — это BitArray. Коллекция типа BitArray поддерживает пораз­ рядные операции, т.е. операции над отдельными двоичными разрядами, например И иди исключающее ИЛИ, а следовательно, она существенно отличается своими воз­ можностями от остальных типов коллекций. Коллекция типа BitArray объявляется в пространстве имен System.Collections.

Обобщенные коллекции обеспечивают обобщенную реализацию нескольких стан­ дартных структур данных, включая связные списки, стеки, очереди и словари. Такие коллекции являются типизированными в силу их обобщенного характера. Это озна­ чает, что в обобщенной коллекции могут храниться только такие элементы данных, которые совместимы по типу с данной коллекцией. Благодаря этому исключается случайное несовпадение типов. Обобщенные коллекции объявляются в пространстве имен System.Collections.Generic.

Параллельные коллекции поддерживают многопоточный доступ к коллекции. Это обобщенные коллекции, определенные в пространстве имен System.Collections. Concurrent.

В пространстве имен System.Collections.ObjectModel находится также ряд классов, поддерживающих создание пользователями собственных обобщенных кол­ лекций.

Основополагающим для всех коллекций является понятие перечислителя, который поддерживается в необобщенных интерфейсах IEnumerator и IEnumerable, а также в обобщенных интерфейсах IEnumerator и IEnumerable. Перечислитель обе­ спечивает стандартный способ поочередного доступа к элементам коллекции. Следо­ вательно, он перечисляет содержимое коллекции. В каждой коллекции должна быть реализована обобщенная или необобщенная форма интерфейса IEnumerable, поэто­ му элементы любого класса коллекции должны быть доступны посредством методов, определенных в интерфейсе IEnumerator или IEnumerator. Это означает, что, внеся минимальные изменения в код циклического обращения к коллекции одного типа, его можно использовать для аналогичного обращения к коллекции другого типа. Любопытно, что для поочередного обращения к содержимому коллекции в цикле foreach используется перечислитель.

Основополагающим для всех коллекций является понятие перечислителя, который поддерживается в необобщенных интерфейсах IEnumerator и IEnumerable, а также в обобщенных интерфейсах IEnumerator и IEnumerable. Перечислитель обе­ спечивает стандартный способ поочередного доступа к элементам коллекции. Следо­ вательно, он перечисляет содержимое коллекции. В каждой коллекции должна быть реализована обобщенная или необобщенная форма интерфейса IEnumerable, поэто­ му элементы любого класса коллекции должны быть доступны посредством методов, определенных в интерфейсе IEnumerator или IEnumerator. Это означает, что, внеся минимальные изменения в код циклического обращения к коллекции одного типа, его можно использовать для аналогичного обращения к коллекции другого типа. Любопытно, что для поочередного обращения к содержимому коллекции в цикле foreach используется перечислитель.

С перечислителем непосредственно связано другое средство, называемое итерато­ ром. Это средство упрощает процесс создания классов коллекций, например специ­ альных, поочередное обращение к которым организуется в цикле foreach. Итераторы также рассматриваются в этой главе.

И последнее замечание: если у вас имеется некоторый опыт программирования на C++, то вам, вероятно, будет полезно знать, что классы коллекций по своей сути по­ добны классам стандартной библиотеки шаблонов (Standard Template Library — STL), определенной в C++. То, что в программировании на C++ называется контейнером, в программировании на C# называется коллекцией. Это же относится и к Java. Если вы знакомы с библиотекой Collections Framework для Java, то научиться пользоваться коллекциями в C# не составит для вас большого труда.

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

Необобщенные коллекции

Необобщенные коллекции вошли в состав среды .NET Framework еще в версии 1.0. Они определяются в пространстве имен System.Collections. Необобщенные коллек­ ции представляют собой структуры данных общего назначения, оперирующие ссылка­ ми на объекты. Таким образом, они позволяют манипулировать объектом любого типа, хотя и не типизированным способом. В этом состоит их преимущество и в то же вре­ мя недостаток. Благодаря тому что необобщенные коллекции оперируют ссылками на объекты, в них можно хранить разнотипные данные. Это удобно в тех случаях, когда тре­ буется манипулировать совокупностью разнотипных объектов или же когда типы хра­ нящихся в коллекции объектов заранее неизвестны. Но если коллекция предназначается для хранения объекта конкретного типа, то необобщенные коллекции не обеспечивают типовую безопасность, которую можно обнаружить в обобщенных коллекциях.

Необобщенные коллекции определены в ряде интерфейсов и классов, реализую­ щих эти интерфейсы. Все они рассматриваются далее по порядку.

Интерфейсы необобщенных коллекций

В пространстве имен System.Collections определен целый ряд интерфейсов необобщенных коллекций. Начинать рассмотрение необобщенных коллекций следу­ ет именно с интерфейсов, поскольку они определяют функциональные возможности, которые являются общими для всех классов необобщенных коллекций. Интерфейсы, служащие опорой для необобщенных коллекций, сведены в табл. 25.1. Каждый из этих интерфейсов подробно описывается далее.

Таблица 25.1. Интерфейсы необобщенных коллекций

Интерфейс Описание
ICollection Определяет элементы, которые должны иметь все необобщенные коллекции
IComparer Определяет метод Compare() для сравнения объектов, хранящихся в коллекции
IDictionary Определяет коллекцию, состоящую из пар "ключ-значение"
IDictionaryEnumerator Определяет перечислитель для коллекции, реализующей интерфейс IDictionary
IEnumerable Определяет метод GetEnumerator(), предоставляющий перечислитель для любого класса коллекции
IEnumerator Предоставляет методы, позволяющие получать содержимое коллекции по очереди
IEqualityComparer Сравнивает два объекта на предмет равенства
IHashCodeProvider Считается устаревшим. Вместо него следует использовать интерфейс IEqualityComparer
IList Определяет коллекцию, доступ к которой можно получить с помощью индексатора
IStructuralComparable Определяет метод CompareTo(), применяемый для структурного сравнения
IStructuralEquatable Определяет метод Equals(), применяемый для выяснения структурного, а не ссылочного равенства. Кроме того, определяет метод GetHashCode()

Интерфейс ICollection

Интерфейс ICollection служит основанием, на котором построены все необобще­ нные коллекции. В нем объявляются основные методы и свойства для всех необобщен­ ных коллекций. Он также наследует от интерфейса IEnumerable.

В интерфейсе ICollection определяются перечисленные ниже свойства.

Свойство Назначение
int Count { get; } Содержит количество элементов в коллекции на данный момент
bool IsSynchronized { get; } Принимает логическое значение true, если коллекция синхронизирована, а иначе — логическое значение false. По умолчанию коллекции не синхронизированы. Но для большинства коллекций можно получить синхронизированный вариант
object SyncRoot { get; } Содержит объект, для которого коллекция может быть синхронизирована

Свойство Count используется чаще всего, поскольку оно содержит количество эле­ ментов, хранящихся в коллекции на данный момент. Если значение свойства Count равно нулю, то коллекция считается пустой.

В интерфейсе ICollection определяется следующий метод.

void СоруТо(Array target, int startIdx)

Метод СоруТо() копирует содержимое коллекции в массив target, начиная с эле­ мента, указываемого по индексу startIdx. Следовательно, метод СоруТо() обеспечи­ вает в C# переход от коллекции к стандартному массиву.

Благодаря тому что интерфейс ICollection наследует от интерфейса IEnumerable, в его состав входит также единственный метод, определенный в интерфейсе IEnumerable. Это метод GetEnumerator(), объявляемый следующим образом.

IEnumerator GetEnumerator()

Он возвращает перечислитель для коллекции.

Вследствие того же наследования от интерфейса IEnumerable в интерфей­ се ICollection определяются также четыре следующих метода расширения: AsParallel(), AsQueryable(), Cast() и OfType(). В частности, метод AsParallel() объявляется в классе System.Linq.ParallelEnumerable, метод AsQueryable() — в классе System.Linq.Queryable, а методы Cast() и OfType() — в классе System. Linq.Enumerable. Эти методы предназначены главным образом для поддержки LINQ, хотя их можно применять и в других целях.

Интерфейс IList

В интерфейсе IList объявляется такое поведение необобщенной коллекции, ко­ торое позволяет осуществлять доступ к ее элементам по индексу с отсчетом от нуля. Этот интерфейс наследует от интерфейсов ICollection и IEnumerable. Помимо методов, определенных в этих интерфейсах, в интерфейсе IList определяется ряд собственных методов. Все эти методы сведены в табл. 25.2. В некоторых из них преду­ сматривается модификация коллекции. Если же коллекция доступна только для чте­ ния или имеет фиксированный размер, то в этих методах генерируется исключение NotSupportedException.

Таблица 25.2. Методы, определенные в интерфейсе IList

Метод Описание
int Add(object value) Добавляет объект value в вызывающую коллекцию. Возвращает индекс, по которому этот объект сохраняется
void Clear() Удаляет все элементы из вызывающей коллекции bool Contains (object value) Возвращает логическое значение true, если вызывающая коллекция содержит объект value, а иначе — логическое значение false
int IndexOf(object value) Возвращает индекс объекта value, если этот объект содержится в вызывающей коллекции. Если же объект value не обнаружен, то метод возвращает значение -1
void Insert(int index, object value) Вставляет в вызывающую коллекцию объект value по индексу index. Элементы, находившиеся до этого по индексу index и дальше, смещаются вперед, чтобы освободить место для вставляемого объекта value
void Remove(object value) Удаляет первое вхождение объекта value в вызывающей коллекции. Элементы, находившиеся до этого за удаленным элементом, смещаются назад, чтобы устранить образовавшийся “пробел"
void RemoveAt(int index) Удаляет из вызывающей коллекции объект, расположенный по указанному индексу index. Элементы, находившиеся до этого за удаленным элементом, смещаются назад, чтобы устранить образовавшийся “пробел”

Объекты добавляются в коллекцию типа IList вызовом метода Add(). Обрати­ те внимание на то, что метод Add() принимает аргумент типа object. А поскольку object является базовым классом для всех типов, то в необобщенной коллекции мо­ жет быть сохранен объект любого типа, включая и типы значений, в силу автоматиче­ ской упаковки и распаковки.

Для удаления элемента из коллекции служат методы Remove() и RemoveAt(). В част­ ности, метод Remove() удаляет указанный объект, а метод RemoveAt() удаляет объект по указанному индексу. И для опорожнения коллекции вызывается метод Clear().

Для того чтобы выяснить, содержится ли в коллекции конкретный объект, вызыва­ ется метод Contains(). Для получения индекса объекта вызывается метод IndexOf(), а для вставки элемента в коллекцию по указанному индексу — метод Insert().

В интерфейсе IList определяются следующие свойства.

bool IsFixedSize { get; }
bool IsReadOnly { get; }

Если коллекция имеет фиксированный размер, то свойство IsFixedSize содержит логическое значение true. Это означает, что в такую коллекцию нельзя ни вставлять элементы, ни удалять их из нее. Если же коллекция доступна только для чтения, то свойство IsReadOnly содержит логическое значение true. Это означает, что содержи­ мое такой коллекции не подлежит изменению.

Кроме того, в интерфейсе IList определяется следующий индексатор.

object this[int index] { get; set; }

Этот индексатор служит для получения и установки значения элемента коллекции. Но его нельзя использовать для добавления в коллекцию нового элемента. С этой це­ лью обычно вызывается метод Add(). Как только элемент будет добавлен в коллекцию, он станет доступным посредством индексатора.

Интерфейс IDictionary

В интерфейсе IDictionary определяется такое поведение необобщенной кол­ лекции, которое позволяет преобразовать уникальные ключи в соответствующие зна­ чения. Ключ представляет собой объект, с помощью которого значение извлекается впоследствии. Следовательно, в коллекции, реализующей интерфейс IDictionary, хранятся пары "ключ-значение". Как только подобная пара будет сохранена, ее мож­ но извлечь с помощью ключа. Интерфейс IDictionary наследует от интерфейсов ICollection и IEnumerable. Методы, объявленные в интерфейсе IDictionary, све­ дены в табл. 25.3. Некоторые из них генерируют исключение ArgumentNullException при попытке указать пустой ключ, поскольку пустые ключи не допускаются.

Таблица 25.3. Методы, определенные в интерфейсе IDictionary

Метод Описание
void Add(object key, object value) Добавляет в вызывающую коллекцию пару "ключ-значение”, определяемую параметрами key и value
void Clear() Удаляет все пары "ключ-значение" из вызывающей коллекции
bool Contains(object key) Возвращает логическое значение true, если вызывающая коллекция содержит объект key в качестве ключа, в противном случае — логическое значение false
IDictionaryEnumerator GetEnumerator() Возвращает перечислитель для вызывающей коллекции
void Remove(object key) Удаляет из коллекции элемент, ключ которого равен значению параметра key

Для добавления пары "ключ-значение" в коллекцию типа IDictionary служит метод Add(). Обратите внимание на то, что ключ и его значение указываются отдель­ но. А для удаления элемента из коллекции следует указать ключ этого объекта при вызове метода Remove(). И для опорожнения коллекции вызывается метод Clear().

Для того чтобы выяснить, содержит ли коллекция конкретный объект, вызыва­ ется метод Contains() с указанным ключом искомого элемента. С помощью мето­ да GetEnumerator() получается перечислитель, совместимый с коллекцией типа IDictionary. Этот перечислитель оперирует парами "ключ-значение".

В интерфейсе IDictionary определяются перечисленные ниже свойства.

Свойство Назначение
bool IsFixedSize { get; } Принимает логическое значение true, если словарь имеет фиксированный размер
bool IsReadOnly { get; } Принимает логическое значение true, если словарь доступен только для чтения
ICollection Keys { get; } Получает коллекцию ключей
ICollection Values { get; } Получает коллекцию значений

Следует иметь в виду, что ключи и значения, содержащиеся в коллекции, доступны в отдельных списках с помощью свойств Keys и Values.

Кроме того, в интерфейсе IDictionary определяется следующий индексатор.

object this[object key] { get; set; }

Этот индексатор служит для получения и установки значения элемента коллекции, а также для добавления в коллекцию нового элемента. Но в качестве индекса в данном случае служит ключ элемента, а не собственно индекс.

Интерфейсы IEnumerable, IEnumerator и IDictionaryEnumerator

Интерфейс IEnumerable является необобщенным, и поэтому он должен быть реализован в классе для поддержки перечислителей. Как пояснялось выше, интер­ фейс IEnumerable реализуется во всех классах необобщенных коллекций, посколь­ ку он наследуется интерфейсом ICollection. Ниже приведен единственный метод GetEnumerator(), определяемый в интерфейсе IEnumerable.

IEnumerator GetEnumerator()

Он возвращает коллекцию. Благодаря реализации интерфейса IEnumerable мож­ но также получать содержимое коллекции в цикле foreach.

В интерфейсе IEnumerator определяются функции перечислителя. С помощью ме­ тодов этого интерфейса можно циклически обращаться к содержимому коллекции. Если в коллекции содержатся пары "ключ-значение" (словари), то метод GetEnumerator() возвращает объект типа IDictionaryEnumerator, а не типа IEnumerator. Интерфейс IDictionaryEnumerator наследует от интерфейса IEnumerator и вводит дополни­ тельные функции, упрощающие перечисление словарей.

В интерфейсе IEnumerator определяются также методы MoveNext() и Reset() и свойство Current. Способы их применения подробнее описываются далее в этой главе. А до тех пор следует отметить, что свойство Current содержит элемент, полу­ чаемый в текущий момент. Метод MoveNext() осуществляет переход к следующему элементу коллекции, а метод Reset() возобновляет перечисление с самого начала.

Интерфейсы IComparer и IEqualityComparer

В интерфейсе IComparer определяется метод Compare() для сравнения двух объектов.

int Compare(object х, object у)

Он возвращает положительное значение, если значение объекта х больше, чем у объекта у; отрицательное — если значение объекта х меньше, чем у объекта у; и ну­ левое — если сравниваемые значения равны. Данный интерфейс можно использовать для указания способа сортировки элементов коллекции.

В интерфейсе IEqualityComparer определяются два метода.

bool Equals(object х, object у)
int GetHashCode(object obj)

Метод Equals() возвращает логическое значение true, если значения объектов х и у равны. А метод GetHashCode() возвращает хеш-код для объекта obj.

Интерфейсы IStructuralComparable и IStructuralEquatable

Оба интерфейса IStructuralComparable и IStructuralEquatable добавлены в версию 4.0 среды .NET Framework. В интерфейсе IStructuralComparable определя­ ется метод CompareTo(), который задает способ структурного сравнения двух объектов для целей сортировки. (Иными словами, Метод CompareTo() сравнивает содержимое объектов, а не ссылки на них.) Ниже приведена форма объявления данного метода.

int CompareTo(object other, IComparer comparer)

Он должен возвращать -1, если вызывающий объект предшествует другому объекту other; 1, если вызывающий объект следует после объекта other; и наконец, 0, если значения обоих объектов одинаковы для целей сортировки. А само сравнение обеспе­ чивает объект, передаваемый через параметр comparer.

Интерфейс IStructuralEquatable служит для выяснения структурного равен­ ства путем сравнения содержимого двух объектов. В этом интерфейсе определены сле­ дующие методы.

bool Equals(object other, IEqualityComparer comparer)
int GetHashCode(IEqualityComparer comparer)

Метод Equals() должен возвращать логическое значение true, если вызывающий объект и другой объект other равны. А метод GetHashCode() должен возвращать хеш-код для вызывающего объекта. Результаты, возвращаемые обоими методами, должны быть совместимы. Само сравнение обеспечивает объект, передаваемый через параметр comparer.

Структура DictionaryEntry

В пространстве имен System.Collections определена структура DictionaryEntry. Необобщенные коллекции пар "ключ-значение" сохраняют эти пары в объекте типа DictionaryEntry. В данной структуре определяются два следующих свойства.

public object Key { get; set; }
public object Value { get; set; }

Эти свойства служат для доступа к ключу или значению, связанному с элементом коллекции. Объект типа DictionaryEntry может быть сконструирован с помощью конструктора:

public DictionaryEntry(object key, object value)

где key обозначает ключ, a value — значение.

Классы необобщенных коллекций

А теперь, когда представлены интерфейсы необобщенных коллекций, можно пе­ рейти к рассмотрению стандартных классов, в которых они реализуются. Ниже при­ ведены классы необобщенных коллекций, за исключением коллекции типа BitArray, рассматриваемой далее в этой главе.

Класс Описание
ArrayList Определяет динамический массив, т.е. такой массив, который может при необходимости увеличивать свой размер
Hashtable Определяет хеш-таблицу для пар “ключ-значение”
Queue Определяет очередь, или список, действующий по принципу “первым пришел — первым обслужен”
SortedList Определяет отсортированный список пар “ключ-значение”
Stack Определяет стек, или список, действующий по принципу "первым пришел — последним обслужен”

Каждый из этих классов коллекций подробно рассматривается и демонстрируется далее на конкретных примерах.

Класс ArrayList

В классе ArrayList поддерживаются динамические массивы, расширяющиеся и сокращающиеся по мере необходимости. В языке C# стандартные массивы имеют фик­ сированную длину, которая не может изменяться во время выполнения программы. Это означает, что количество элементов в массиве нужно знать заранее. Но иногда тре­ буемая конкретная длина массива остается неизвестной до самого момента выполне­ ния программы. Именно для таких ситуаций и предназначен класс ArrayList. В клас­ се ArrayList определяется массив переменной длины, который состоит из ссылок на объекты и может динамически увеличивать и уменьшать свой размер. Массив типа ArrayList создается с первоначальным размером. Если этот размер превышается, то массив автоматически расширяется. А при удалении объектов из такого массива он автоматически сокращается. Коллекции класса ArrayList широко применяются в практике программирования на С#. Именно поэтому они рассматриваются здесь под­ робно. Но многие способы применения коллекций класса ArrayList распространя­ ются и на другие коллекции, в том числе и на обобщенные.

В классе ArrayList реализуются интерфейсы ICollection, IList, IEnumerable и ICloneable. Ниже приведены конструкторы класса ArrayList.

public ArrayList()
public ArrayList(ICollection c)
public ArrayList(int capacity)

Первый конструктор создает пустую коллекцию класса ArrayList с нулевой перво­ начальной емкостью. Второй конструктор создает коллекцию типа ArrayList с коли­ чеством инициализируемых элементов, которое определяется параметром с и равно первоначальной емкости массива. Третий конструктор создает коллекцию, имеющую указанную первоначальную емкость, определяемую параметром сараcity. В данном случае емкость обозначает размер базового массива, используемого для хранения эле­ ментов коллекции. Емкость коллекции типа ArrayList может увеличиваться автома­ тически по мере добавления в нее элементов.

В классе ArrayList определяется ряд собственных методов, помимо тех, что уже объявлены в интерфейсах, которые в нем реализуются. Некоторые из наиболее часто используемых методов класса ArrayList перечислены в табл. 25.4. Коллекцию класса ArrayList можно отсортировать, вызвав метод Sort(). В этом случае поиск в отсо­ ртированной коллекции с помощью метода BinarySearch() становится еще более эффективным. Содержимое коллекции типа ArrayList можно также обратить, вы­ звав метод Reverse().

Таблица 25.4. Наиболее часто используемые методы, определенные в классе ArrayList

Метод Описание
public virtual void AddRange(Icollection с) Добавляет элементы из коллекции с в конец вызывающей коллекции типа ArrayList
public virtual int BinarySearch(object value) Выполняет поиск в вызывающей коллекции значения value. Возвращает индекс найденного элемента. Если искомое значение не найдено, возвращает отрицательное значение. Вызывающий список должен быть отсортирован
public virtual int BinarySearch(object value, Icomparer comparer) Выполняет поиск в вызывающей коллекции значения value, используя для сравнения способ, определяемый параметром comparer. Возвращает индекс совпавшего элемента. Если искомое значение не найдено, возвращает отрицательное значение. Вызывающий список должен быть отсортирован
public virtual int BinarySearch(int index, int count, object value, IComparer comparer) Выполняет поиск в вызывающей коллекции значения value, используя для сравнения способ, определяемый параметром comparer. Поиск начинается с элемента, указываемого по индексу index, и включает количество элементов, определяемых параметром count. Метод возвращает индекс совпавшего элемента. Если искомое значение не найдено, метод возвращает отрицательное значение. Вызывающий список должен быть отсортирован
public virtual void СоруТо(Array array) Копирует содержимое вызывающей коллекции в массив array, который должен быть одномерным и совместимым по типу с элементами коллекции
public virtual void СоруТо(Array array, int arrayIndex) Копирует содержимое вызывающей коллекции в массив array, начиная с элемента, указываемого по индексу arrayIndex. Целевой массив должен быть одномерным и совместимым по типу с элементами коллекции
public virtual void CopyTo(int index, Array array, int arrayIndex, int count) Копирует часть вызывающей коллекции, начиная с элемента, указываемого по индексу index, и включая количество элементов, определяемых параметром count, в массив array, начиная с элемента, указываемого по индексу arrayIndex. Целевой массив должен быть одномерным и совместимым по типу с элементами коллекции
public static ArrayList FixedSize(ArrayList list) public virtual ArrayList GetRange(int index, int count) Заключает коллекцию list в оболочку типа ArrayList с фиксированным размером и возвращает результат Возвращает часть вызывающей коллекции типа ArrayList. Часть возвращаемой коллекции начинается с элемента, указываемого по индексу index, и включает количество элементов, определяемое параметром count. Возвращаемый объект ссылается на те же элементы, что и вызывающий объект
public virtual int IndexOf(object value) Возвращает индекс первого вхождения объекта value в вызывающей коллекции. Если искомый объект не обнаружен, возвращает значение -1
public virtual void InsertRange(int index, ICollection c) Вставляет элементы коллекции с в вызывающую коллекцию, начиная с элемента, указываемого по индексу index
public virtual int LastlndexOf(object value) Возвращает индекс последнего вхождения объекта value в вызывающей коллекции. Если искомый объект не обнаружен, метод возвращает значение -1
public static ArrayList Readonly(ArrayList list) Заключает коллекцию list в оболочку типа ArrayList, доступную только для чтения, и возвращает результат
public virtual void RemoveRange(int index, int count) Удаляет часть вызывающей коллекции, начиная с элемента, указываемого по индексу index, и включая количество элементов, определяемое параметром count
public virtual void Reverse() Располагает элементы вызывающей коллекции в обратном порядке
public virtual void Reverse(int index, int count) Располагает в обратном порядке часть вызывающей коллекции, начиная с элемента, указываемого по индексу index, и включая количество элементов, определяемое параметром count
public virtual void SetRange(int index, ICollection c) Заменяет часть вызывающей коллекции, начиная с элемента, указываемого по индексу index, элементами коллекции с
public virtual void Sort() Сортирует вызывающую коллекцию по нарастающей
public virtual void Sort(Icomparer comparer) Сортирует вызывающую коллекцию, используя для сравнения способ, определяемый параметром comparer. Если параметр comparer имеет пустое значение, то для сравнения используется способ, выбираемый по умолчанию
public virtual void Sort(int index, int count, Icomparer comparer) Сортирует вызывающую коллекцию, используя для сравнения способ, определяемый параметром comparer. Сортировка начинается с элемента, указываемого по индексу index, и включает количество элементов, определяемых параметром count. Если параметр comparer имеет пустое значение, то для сравнения используется способ, выбираемый по умолчанию
public static ArrayList Synchronized(ArrayList list) Возвращает синхронизированный вариант коллекции типа ArrayList, передаваемой в качестве параметра list
public virtual object[] ToArray() Возвращает массив, содержащий копии элементов вызывающего объекта
public virtual Array ToArray(Type type) Возвращает массив, содержащий копии элементов вызывающего объекта. Тип элементов этого массива определяется параметром type
public virtual void TrimToSize() Устанавливает значение свойства Capacity равным значению свойства Count

В классе ArrayList поддерживается также ряд методов, оперирующих элемента­ ми коллекции в заданных пределах. Так, в одну коллекцию типа ArrayList можно вставить другую коллекцию, вызвав метод InsertRange(). Для удаления из коллек­ ции элементов в заданных пределах достаточно вызвать метод RemoveRange(). А для

перезаписи элементов коллекции типа ArrayList в заданных пределах элементами из другой коллекции служит метод SetRange(). И наконец, элементы коллекции можно сортировать или искать в заданных пределах, а не во всей коллекции. По умолчанию коллекция типа ArrayList не синхронизирована. Для получения синхронизированной оболочки, в которую заключается коллекция, вызывается метод Synchronized().

В классе ArrayList имеется также приведенное ниже свойство Capacity, помимо свойств, определенных в интерфейсах, которые в нем реализуются.

public virtual int Capacity { get; set; }

Свойство Capacity позволяет получать и устанавливать емкость вызывающей кол­ лекции типа ArrayList. Емкость обозначает количество элементов, которые может содержать коллекция типа ArrayList до ее вынужденного расширения. Как упоми­ налось выше, коллекция типа ArrayList расширяется автоматически, и поэтому за­ давать ее емкость вручную необязательно. Но из соображений эффективности это ино­ гда можно сделать, если количество элементов коллекции известно заранее. Благодаря этому исключаются издержки на выделение дополнительной памяти.

С другой стороны, если требуется сократить размер базового массива коллекции типа ArrayList, то для этой цели достаточно установить меньшее значение свойства Capacity. Но это значение не должно быть меньше значения свойства Count. Напом­ ним, что свойство Count определено в интерфейсе ICollection и содержит количе­ ство объектов, хранящихся в коллекции на данный момент. Всякая попытка установить значение свойства Capacity меньше значения свойства Count приводит к генериро­ ванию исключения ArgumentOutOfRangeException. Поэтому для получения такого количества элементов коллекции типа ArrayList, которое содержится в ней на дан­ ный момент, следует установить значение свойства Capacity равным значению свой­ ства Count. Для этой цели можно также вызвать метод TrimToSize().

В приведенном ниже примере программы демонстрируется применение класса ArrayList. В ней сначала создается коллекция типа ArrayList, а затем в эту коллек­ цию вводятся символы, после чего содержимое коллекции отображается. Некоторые элементы затем удаляются из коллекции, и ее содержимое отображается вновь. После этого в коллекцию вводятся дополнительные элементы, что вынуждает увеличить ее емкость. И наконец, содержимое элементов коллекции изменяется.

// Продемонстрировать применение класса ArrayList.
using System;
using System.Collections;

class ArrayListDemo {
	static void Main() {
		// Создать коллекцию в виде динамического массива.
		ArrayList al = new ArrayList();

		Console.WriteLine("Исходное количество элементов: " + al.Count);

		Console.WriteLine();

		Console.WriteLine("Добавить 6 элементов");
		// Добавить элементы в динамический массив.
		al.Add('С');
		al.Add('A');
		al.Add('E');
		al.Add('В');
		al.Add('D');
		al.Add('F');

		Console.WriteLine("Количество элементов: " + al.Count);

		// Отобразить содержимое динамического массива,
		// используя индексирование массива.
		Console.Write("Текущее содержимое: ");
		for(int i=0; i < al.Count; i++)
			Console.Write(al[i] + " ");
		Console.WriteLine("\n");

		Console.WriteLine("Удалить 2 элемента");
		// Удалить элементы из динамического массива.
		al.Remove('F');
		al.Remove('A');

		Console.WriteLine("Количество элементов: " + al.Count);

		// Отобразить содержимое динамического массива, используя цикл foreach.
		Console.Write("Содержимое: ");
		foreach(char с in al)
			Console.Write(с + " ");
		Console.WriteLine("\n");

		Console.WriteLine("Добавить еще 20 элементов");
		// Добавить количество элементов, достаточное для
		// принудительного расширения массива.
		for (int i=0; i < 20; i++)
			al.Add((char)('a' + i));
		Console.WriteLine("Текущая емкость: " + al.Capacity);
		Console.WriteLine("Количество элементов после добавления 20 новых: " +
						al.Count);
		Console.Write("Содержимое: ");
		foreach(char с in al)
			Console.Write(с + " ");
		Console.WriteLine("\n");

		// Изменить содержимое динамического массива,
		// используя индексирование массива.
		Console.WriteLine("Изменить три первых элемента");
		al[0] = 'X';
		al[1] = 'Y';
		al[2] = 'Z';
		Console.Write("Содержимое: ");
		foreach(char с in al)
			Console.Write(c + " ");
		Console.WriteLine();
	}
}

Вот к какому результату приводит выполнение этой программы.

Исходное количество элементов: 0

Добавить 6 элементов
Количество элементов: 6
Текущее содержимое: С А Е В D F

Удалить 2 элемента
Количество элементов: 4
Содержимое: С Е В D

Добавить еще 20 элементов
Текущая емкость: 32
Количество элементов после добавления 20 новых: 24
Содержимое: C E B D a b c d e f g h i j k l m n o p q r s t

Изменить три первых элемента
Содержимое: X Y Z D a b c d e f g h i j k l m n o p q r s t

Сортировка и поиск в коллекции типа ArrayList

Коллекцию типа ArrayList можно отсортировать с помощью метода Sort(). В этом случае поиск в отсортированной коллекции с помощью метода BinarySearch() становится еще более эффективным. Применение обоих методов демонстрируется в приведенном ниже примере программы.

// Отсортировать коллекцию типа ArrayList и осуществить в ней поиск.
using System;
using System.Collections;

class SortSearchDemo {
	static void Main() {
		// Создать коллекцию в виде динамического массива.
		ArrayList al = new ArrayList();

		// Добавить элементы в динамический массив.
		al.Add(55);
		al.Add(43);
		al.Add(-4);
		al.Add(88);
		al.Add(3);
		al.Add(19);

		Console.Write("Исходное содержимое: ");
		foreach(int i in al)
			Console.Write(i + " ");
		Console.WriteLine("\n");

		// Отсортировать динамический массив.
		al.Sort();

		// Отобразить содержимое динамического массива, используя цикл foreach.
		Console.Write("Содержимое после сортировки: ");
		foreach(int i in al)
			Console.Write(i + " ");
		Console.WriteLine("\n");
		Console.WriteLine("Индекс элемента 43: " +
		al.BinarySearch(43));
	}
}

Ниже приведен результат выполнения этой программы.

Исходное содержимое: 55 43 -4 88 3 19

Содержимое после сортировки: -4 3 19 43 55 88

Индекс элемента 43: 3

В одной и той же коллекции типа ArrayList могут храниться объекты любого типа. Тем не менее во время сортировки и поиска в ней эти объекты приходится срав­ нивать. Так, если бы список объектов в приведенном выше примере программы со­ держал символьную строку, то их сравнение привело бы к исключительной ситуации. Впрочем, для сравнения символьных строк и целых чисел можно создать специальные методы. О таких методах сравнения речь пойдет далее в этой главе.

Получение массива из коллекции типа ArrayList

В работе с коллекцией типа ArrayList иногда требуется получить из ее содер­ жимого обычный массив. Этой цели служит метод ТоАrrау(). Для преобразования коллекции в массив имеется несколько причин. Две из них таковы: потребность в уско­ рении обработки при выполнении некоторых операций и необходимость передавать массив методу, который не перегружается, чтобы принять коллекцию. Но независимо от конкретной причины коллекция типа ArrayList преобразуется в обычный массив довольно просто, как показано в приведенном ниже примере программы.

// Преобразовать коллекцию типа ArrayList в обычный массив.
using System;
using System.Collections;

class ArrayListToArray {
	static void Main() {
		ArrayList al = new ArrayList();

		// Добавить элементы в динамический массив.
		al.Add(1);
		al.Add(2);
		al.Add(3);
		al.Add(4);
		Console.Write("Содержимое: ");
		foreach(int i in al)
			Console.Write(i + " ");
		Console.WriteLine();

		// Получить массив.
		int[] ia = (int[]) al.ToArray(typeof(int));
		int sum = 0;

		// Просуммировать элементы массива.
		for(int i=0; i<ia.Length; i++)
			sum += ia[i];
		Console.WriteLine("Сумма равна: " + sum);
	}
}

Эта программа дает следующий результат.

Содержимое: 1 2 3 4
Сумма равна: 10

В начале этой программы создается коллекция целых чисел. Затем в ней вызыва­ ется метод ToArray() с указанием типа int получаемого массива. В итоге создается целочисленный массив. Но поскольку Array является типом, возвращаемым методом ToArray(), то содержимое подучаемого в итоге массива должно быть приведено к типу int[]. (Напомним, что Array является базовым типом для всех массивов в С#.) И наконец, значения всех элементов массива суммируются.

Класс Hashtable

Класс Hashtable предназначен для создания коллекции, в которой для хранения ее элементов служит хеш-таблица. Как должно быть известно большинству читателей, информация сохраняется в хеш-таблице с помощью механизма, называемого хеширо­ ванием. При хешировании для определения уникального значения, называемого хеш- кодом, используется информационное содержимое специального ключа. Подученный в итоге хеш-код служит в качестве индекса, по которому в таблице хранятся искомые данные, соответствующие заданному ключу. Преобразование ключа в хеш-код выпол­ няется автоматически, и поэтому сам хеш-код вообще недоступен пользователю. Преи­ мущество хеширования заключается в том, что оно обеспечивает постоянство времени выполнения операций поиска, извлечения и установки значений независимо от вели­ чины массивов данных. В классе Hashtable реализуются интерфейсы IDictionary, ICollection, IEnumerable, ISerializable, IDeserializationCallback и ICloneable.

В классе Hashtable определено немало конструкторов. Ниже приведены наиболее часто используемые конструкторы этого класса.

public Hashtable()
public Hashtable(IDictionary d)
public Hashtable(int capacity)
public Hashtable(int capacity,
				float loadFactor)

В первой форме создается создаваемый по умолчанию объект класса Hashtable. Во второй форме создаваемый объект типа Hashtable инициализируется элементами из коллекции d. В третьей форме создаваемый объект типа Hashtable инициализиру­ ется, учитывая емкость коллекции, задаваемую параметром capacity. И в четвертой форме создаваемый объект типа Hashtable инициализируется, учитывая заданную емкость capacity и коэффициент заполнения loadFactor. Коэффициент заполне­ ния, иногда еще называемый коэффициентом загрузки, должен находиться в пределах от 0,1 до 1,0. Он определяет степень заполнения хеш-таблицы до увеличения ее разме­ ра. В частности, таблица расширяется, если количество элементов оказывается больше емкости таблицы, умноженной на коэффициент заполнения. В тех конструкторах, ко­ торые не принимают коэффициент заполнения в качестве параметра, этот коэффици­ ент по умолчанию выбирается равным 1,0.

В классе Hashtable определяется ряд собственных методов, помимо тех, что уже объявлены в интерфейсах, которые в нем реализуются. Некоторые из наиболее часто используемых методов этого класса приведены в табл. 25.5. В частности, для того что­ бы определить, содержится ли ключ в коллекции типа Hashtable, вызывается метод ContainsKey(). А для того чтобы выяснить, хранится ли в такой коллекции конкрет­ ное значение, вызывается метод ContainsValue(). Для перечисления содержимого коллекции типа Hashtable служит метод GetEnumerator(), возвращающий объект типа IDictionaryEnumerator. Напомним, что IDictionaryEnumerator — это перечислитель, используемый для перечисления содержимого коллекции, в которой хранятся пары "ключ-значение".

Таблица 25.5. Наиболее часто используемые методы, определенные в классе Hashtable

Метод Описание
public virtual bool ContainsKey(object key) Возвращает логическое значение true, если в вызывающей коллекции типа Hashtable содержится ключ key, а иначе — логическое значение false
public virtual bool ContainsValue(object value) Возвращает логическое значение true, если в вызывающей коллекции типа Hashtable содержится значение value, а иначе — логическое значение false
public virtual IDictionaryEnumerator GetEnumerator() Возвращает для вызывающей коллекции типа Hashtable перечислитель типа IDictionaryEnumerator
public static Hashtable Synchronized(Hashtable table) Возвращает синхронизированный вариант коллекции типа Hashtable, передаваемой в качестве параметра table

В классе Hashtable доступны также открытые свойства, определенные в тех ин­ терфейсах, которые в нем реализуются. Особая роль принадлежит двум свойствам, Keys и Values, поскольку с их помощью можно получить ключи или значения из коллекции типа Hashtable. Эти свойства определяются в интерфейсе IDictionary следующим образом.

public virtual ICollection Keys { get; }
public virtual ICollection Values { get; }

В классе Hashtable не поддерживаются упорядоченные коллекции, и поэтому ключи или значения получаются из коллекции в произвольном порядке. Кроме того, в классе Hashtable имеется защищенное свойство EqualityComparer. А два других свойства, hcp и comparer, считаются устаревшими.

Пары "ключ-значение" сохраняются в коллекции типа Hashtable в форме струк­ туры типа DictionaryEntry, но чаще всего это делается без прямого вмешательства со стороны пользователя, поскольку свойства и методы оперируют ключами и значе­ ниями по отдельности. Если, например, в коллекцию типа Hashtable добавляется элемент, то для этой цели вызывается метод Add(), принимающий два аргумента: ключ и значение.

Нужно, однако, иметь в виду, что сохранение порядка следования элементов в кол­ лекции типа Hashtable не гарантируется. Дело в том, что процесс хеширования ока­ зывается, как правило, непригодным для создания отсортированных таблиц.

Ниже приведен пример программы, в которой демонстрируется применение клас­ са Hashtable.

// Продемонстрировать применение класса Hashtable.
using System;
using System.Collections;

class HashtableDemo {
	static void Main() {
		// Создать хеш-таблицу.
		Hashtable ht = new Hashtable();

		// Добавить элементы в таблицу.
		ht.Add("здание", "жилое помещение");
		ht.Add("автомашина", "транспортное средство");
		ht.Add("книга", "набор печатных слов");
		ht.Add("яблоко", "съедобный плод");

		// Добавить элементы с помощью индексатора.
		ht["трактор"] = "сельскохозяйственная машина";

		// Получить коллекцию ключей.
		ICollection с = ht.Keys;

		// Использовать ключи для получения значений.
		foreach(string str in с)
			Console.WriteLine(str + ": " + ht[str]);
	}
}

Выполнение этой программы приводит к следующему результату.

здание: жилое помещение
книга: набор печатных слов
трактор: сельскохозяйственная машина
автомашина: транспортное средство
яблоко: съедобный плод

Как следует из приведенного выше результата, пары "ключ-значение" сохраняются в произвольном порядке. Обратите внимание на то, как получено и отображено содер­ жимое хеш-таблицы ht. Сначала была получена коллекция ключей с помощью свой­ ства Keys. Затем каждый ключ был использован для индексирования хеш-таблицы ht с целью извлечь из нее значение, соответствующее заданному ключу. Напомним, что в качестве индекса в данном случае использовался индексатор, определенный в интер­ фейсе IDictionary и реализованный в классе Hashtable.

Класс SortedList

Класс SortedList предназначен для создания коллекции, в которой пары "ключ- значение" хранятся в порядке, отсортированном по значению ключей. В классе SortedList реализуются интерфейсы IDictionary, ICollection, IEnumerable и ICloneable.

В классе SortedList определено несколько конструкторов, включая следующие.

public SortedList()
public SortedList(IDictionary d)
public SortedList(int initialCapacity)
public SortedList(IComparer comparer)

В первом конструкторе создается пустая коллекция, первоначальная емкость которой равна нулю. Во втором конструкторе создается пустая коллекция типа SortedList, которая инициализируется элементами из коллекции d. Ее первоначаль­ ная емкость равна количеству указанных элементов. В третьем конструкторе создается пустая коллекция типа SortedList, первоначальный размер которой определяет ем­ кость, задаваемая параметром initialCapacity. Эта емкость соответствует размеру базового массива, используемого для хранения элементов коллекции. И в четвертой форме конструктора с помощью параметра comparer указывается способ, исполь­ зуемый для сравнения объектов по списку. В этой форме создается пустая коллекция, первоначальная емкость которой равна нулю.

При добавлении новых элементов в список емкость коллекции типа SortedList увеличивается автоматически по мере надобности. Так, если текущая емкость коллек­ ции превышается, то она соответственно увеличивается. Преимущество указания ем­ кости коллекции типа SortedList при ее создании заключается в снижении или пол­ ном исключении издержек на изменение размера коллекции. Разумеется, указывать емкость коллекции целесообразно лишь в том случае, если заранее известно, сколько элементов требуется хранить в ней.

В классе SortedList определяется ряд собственных методов, помимо тех, что уже объявлены в интерфейсах, которые в нем реализуются. Некоторые из наиболее часто используемых методов этого класса перечислены в табл. 25.6. Так, если требу­ ется определить, содержится ли ключ в коллекции типа SortedList, вызывается ме­ тод ContainsКеу(). А если требуется выяснить, хранится ли конкретное значение в коллекции типа SortedList, вызывается метод ContainsValue(). Для перечис­ ления содержимого коллекции типа SortedList служит метод GetEnumerator(), возвращающий объект типа IDictionaryEnumerator. Напомним, что IDictionaryEnumerator — это перечислитель, используемый для перечисления содержимого коллекции, в которой хранятся пары "ключ-значение". И наконец, для получения синхронизированной оболочки, в которую заключается коллекция типа SortedList, вызывается метод Synchronized().

Таблица 25.6. Наиболее часто используемые методы, определенные в классе SortedList

Метод Описание
public virtual bool ContainsKey(object key) Возвращает логическое значение true, если в вызывающей коллекции типа SortedList содержится ключ key, а иначе — логическое значение false
public virtual bool ContainsValue(object value) Возвращает логическое значение true, если в вызывающей коллекции типа SortedList содержится значение value, а иначе — логическое значение false
public virtual object GetBylndex(int index) Возвращает значение, указываемое по индексу index
public virtual IDictionaryEnumerator GetEnumerator() Возвращает для вызывающей коллекции типа SortedList перечислитель типа IDictionaryEnumerator
public virtual object GetKey(int Index) Возвращает значение ключа, указываемое по индексу index
public virtual IList GetKeyList() Возвращает коллекцию типа SortedList с ключами, хранящимися в вызывающей коллекции типа SortedList
public virtual IList GetValueList() Возвращает коллекцию типа SortedList со значениями, хранящимися в вызывающей коллекции типа SortedList
public virtual int IndexOfKey(object key) Возвращает индекс ключа key. Если искомый ключ не обнаружен, возвращается значение -1
public virtual int IndexOfValue(object value) Возвращает индекс первого вхождения значения value в вызывающей коллекции. Если искомое значение не обнаружено, возвращается значение -1
public virtual void SetBylndex(int index, object value) Устанавливает значение по индексу Index равным значению value
public static SortedList Synchronized(SortedList list) Возвращает синхронизированный вариант коллекции типа SortedList, передаваемой в качестве параметра list
public virtual void TrimToSize() Устанавливает значение свойства Capacity равным значению свойства Count

Ключ или значение можно получить разными способами. В частности, для полу­ чения значения по указанному индексу служит метод GetByIndex(), а для установ­ ки значения по указанному индексу — метод SetByIndex(). Для извлечения ключа по указанному индексу вызывается метод GetKey(), а для получения списка ключей по указанному индексу — метод GetKeyList(). Кроме того, для получения списка всех значений из коллекции служит метод GetValueList(). Для получения индекса ключа вызывается метод IndexOfKey(), а для получения индекса значения — метод IndexOfValue(). Безусловно, в классе SortedList также поддерживается индекса­ тор, определяемый в интерфейсе IDictionary и позволяющий устанавливать и по­ лучать значение по заданному ключу.

В классе SortedList доступны также открытые свойства, определенные в тех ин­ терфейсах, которые в нем реализуются. Как и в классе Hashtable, в данном классе особая роль принадлежит двум свойствам, Keys и Values, поскольку с их помощью можно получить доступную только для чтения коллекцию ключей или значений из коллекции типа SortedList. Эти свойства определяются в интерфейсе IDictionary следующим образом.

public virtual ICollection Keys { get; }
public virtual ICollection Values { get; }

Порядок следования ключей и значений отражает порядок их расположения в кол­ лекции типа SortedList.

Аналогично коллекции типа Hashtable, пары "ключ-значение" сохраняются в коллекции типа SortedList в форме структуры типа DictionaryEntry, но, как правило, доступ к ключам и значениям осуществляется по отдельности с помощью методов и свойств, определенных в классе SortedList.

В приведенном ниже примере программы демонстрируется применение класса SortedList. Это переработанный и расширенный вариант предыдущего примера, демонстрировавшего применение класса Hashtable, вместо которого теперь исполь­ зуется класс SortedList. Глядя на результат выполнения этой программы, вы можете сами убедиться, что теперь список полученных значений оказывается отсортирован­ ным по заданному ключу.

// Продемонстрировать применение класса SortedList.
using System;
using System.Collections;
class SLDemo {
    static void Main() {
        // Создать отсортированный список.
        SortedList sl = new SortedList();

        // Добавить элементы в список.
        sl.Add("здание", "жилое помещение");
        sl.Add("автомашина", "транспортное средство");
        sl.Add("книга", "набор печатных слов");
        sl.Add("яблоко", "съедобный плод");

        // Добавить элементы с помощью индексатора,
        sl["трактор"] = "сельскохозяйственная машина";

        // Получить коллекцию ключей.
        ICollection с = sl.Keys;

        // Использовать ключи для получения значений.
        Console.WriteLine("Содержимое списка по индексатору.");
        foreach(string str in с)
            Console.WriteLine(str + ": " + sl[str]);
        Console.WriteLine();

        // Отобразить список, используя целочисленные индексы.
        Console.WriteLine("Содержимое списка по целочисленным индексам.");
        for(int i=0; i < sl.Count; i++)
            Console.WriteLine(sl.GetByIndex(i));
        Console.WriteLine();

        // Показать целочисленные индексы элементов списка.
        Console.WriteLine("Целочисленные индексы элементов списка.");
        foreach(string str in с)
            Console.WriteLine(str + ": " + si.IndexOfKey (str));
    }
}

Ниже приведен результат выполнения этой программы.

Содержимое списка по индексатору.
автомашина: транспортное средство
здание: жилое помещение
книга: набор печатных слов
трактор: сельскохозяйственная машина
яблоко: съедобный плод

Содержимое списка по целочисленным индексам.
транспортное средство
жилое помещение
набор печатных слов
сельскохозяйственная машина
съедобный плод

Целочисленные индексы элементов списка.
автомашина: 0
здание: 1
книга: 2
трактор: 3
яблоко: 4

Класс Stack

Как должно быть известно большинству читателей, стек представляет собой спи­ сок, действующий по принципу "первым пришел — последним обслужен". Этот принцип действия стека можно наглядно представить на примере горки тарелок, стоя­ щих на столе. Первая тарелка, поставленная в эту горку, извлекается из нее последней. Стек относится к одним из самых важных структур данных в вычислительной техни­ ке. Он нередко применяется, среди прочего, в системном программном обеспечении, компиляторах, а также в программах отслеживания в обратном порядке на основе ис­ кусственного интеллекта.

Класс коллекции, поддерживающий стек, носит название Stack. В нем реализуют­ ся интерфейсы ICollection, IEnumerable и ICloneable. Этот класс создает дина­ мическую коллекцию, которая расширяется по мере потребности хранить в ней вво­ димые элементы. Всякий раз, когда требуется расширить такую коллекцию, ее емкость увеличивается вдвое.

В классе Stack определяются следующие конструкторы.

public Stack()
public Stack(int initialCapacity)
public Stack(ICollection col)

В первой форме конструктора создается пустой стек, во второй форме — пустой стек, первоначальный размер которого определяет первоначальная емкость, задавае­ мая параметром initialCapacity, и в третьей форме — стек, содержащий элементы указываемой коллекции col. Его первоначальная емкость равна количеству указанных элементов.

В классе Stack определяется ряд собственных методов, помимо тех, что уже объяв­ лены в интерфейсах, которые в нем реализуются. Некоторые из наиболее часто исполь­ зуемых методов этого класса приведены в табл. 25.7. Эти методы обычно применяются следующим образом. Для того чтобы поместить объект на вершине стека, вызывается метод Push(). А для того чтобы извлечь и удалить объект из вершины стека, вызыва­ ется метод Pop(). Если же объект требуется только извлечь, но не удалить из вершины стека, то вызывается метод Рееk(). А если вызвать метод Pop() или Рееk(), когда вы­ зывающий стек пуст, то сгенерируется исключение InvalidOperationException.

Таблица 25.7. Наиболее часто используемые методы, определенные в классе Stack

Метод Описание
public virtual void Clear() Устанавливает свойство Count равным нулю, очищая, по существу, стек
public virtual bool Contains (object obj) Возвращает логическое значение true, если объект obj содержится в вызывающем стеке, а иначе — логическое значение false
public virtual object Peek() Возвращает элемент, находящийся на вершине стека, но не удаляет его
public virtual object Pop() Возвращает элемент, находящийся на вершине стека, удаляя его по ходу дела
public virtual void Push (object obj) Помещает объект obj в стек
public static Stack Synchronized(Stack stack) Возвращает синхронизированный вариант коллекции типа Stack, передаваемой в качестве параметра stack
public virtual object[] ToArray() Возвращает массив, содержащий копии элементов вызывающего стека

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

// Продемонстрировать применение класса Stack.
using System;
using System.Collections;

class StackDemo {
	static void ShowPush(Stack st, int a) {
		st.Push(a);
		Console.WriteLine("Поместить в стек: Push(" + a + ")");
		Console.Write("Содержимое стека: ");
		foreach(int i in st)
			Console.Write(i + " ");

		Console.WriteLine();
	}

	static void ShowPop(Stack st) {
		Console.Write("Извлечь из стека: Pop -> ");
		int a = (int) st.Pop();

		Console.WriteLine(a);

		Console.Write("Содержимое стека: ");
		foreach(int i in st)
			Console.Write(i + " ");

		Console.WriteLine();
	}

	static void Main() {
		Stack st = new Stack ();

		foreach(int i in st)
			Console.Write(i + " ");

		Console.WriteLine();

		ShowPush(st, 22);
		ShowPush(st, 65);
		ShowPush(st, 91);
		ShowPop(st);
		ShowPop(st);
		ShowPop(st);

		try {
			ShowPop(st);
		} catch (InvalidOperationException) {
			Console.WriteLine("Стек пуст.");
		}
	}
}

Ниже приведен результат выполнения этой программы. Обратите внимание на то, как обрабатывается исключение InvalidOperationException, генерируемое при попытке извлечь элемент из пустого стека.

Поместить в стек: Push (22)
Содержимое стека: 22
Поместить в стек: Push(65)
Содержимое стека: 65 22
Поместить в стек: Push (91)
Содержимое стека: 91 65 22
Извлечь из стека: Pop -> 91
Содержимое стека: 65 22
Извлечь из стека: Pop -> 65
Содержимое стека: 22
Извлечь из стека: Pop -> 22
Содержимое стека:
Извлечь из стека: Pop -> Стек пуст.

Класс Queue

Еще одной распространенной структурой данных является очередь, действующая по принципу: первым пришел — первым обслужен. Это означает, что первым из оче­ реди извлекается элемент, помещенный в нее первым. Очереди часто встречаются в реальной жизни. Многим из нас нередко приходилось стоять в очередях к кассе в бан­ ке, магазине или столовой. В программировании очереди применяются для хранения таких элементов, как процессы, выполняющиеся в данный момент в системе, списки приостановленных транзакций в базе данных или пакеты данных, полученные по Ин­ тернету. Кроме того, очереди нередко применяются в области имитационного моде­ лирования.

Класс коллекции, поддерживающий очередь, носит название Queue. В нем реали­ зуются интерфейсы ICollection, IEnumerable и ICloneable. Этот класс создает динамическую коллекцию, которая расширяется, если в ней необходимо хранить вво­ димые элементы. Так, если в очереди требуется свободное место, ее размер увеличива­ ется на коэффициент роста, который по умолчанию равен 2,0.

В классе Queue определяются приведенные ниже конструкторы.

public Queue()
public Queue (int capacity)
public Queue (int capacity, float growFactor)
public Queue (ICollection col)

В первой форме конструктора создается пустая очередь с выбираемыми по умол­ чанию емкостью и коэффициентом роста 2,0. Во второй форме создается пустая оче­ редь, первоначальный размер которой определяет емкость, задаваемая параметром сараcity, а коэффициент роста по умолчанию выбирается для нее равным 2,0. В тре­ тьей форме допускается указывать не только емкость (в качестве параметра capacity), но и коэффициент роста создаваемой очереди (в качестве параметра growFactor в пределах от 1,0 до 10,0). И в четвертой форме создается очередь, состоящая из элемен­ тов указываемой коллекции col. Ее первоначальная емкость равна количеству указан­ ных элементов, а коэффициент роста по умолчанию выбирается для нее равным 2,0. В классе Queue определяется ряд собственных методов, помимо тех, что уже объявлены в интерфейсах, которые в нем реализуются. Некоторые из наиболее ча­ сто используемых методов этого класса перечислены в табл. 25.8. Эти методы обыч­ но применяются следующим образом. Для того чтобы поместить объект в очередь, вызывается метод Enqueue(). Если требуется извлечь и удалить первый объект из начала очереди, то вызывается метод Dequeue(). Если же требуется извлечь, но не удалять следующий объект из очереди, то вызывается метод Рееk(). А если методы Dequeue() и Рееk() вызываются, когда очередь пуста, то генерируется исключение InvalidOperationException.

Таблица 25.8. Наиболее часто используемые методы, определенные в классе Queue

Метод Описание
public virtual void Clear() Устанавливает свойство Count равным нулю, очищая, по существу, очередь
public virtual bool Contains(object obj) Возвращает логическое значение true, если объект obj содержится в вызывающей очереди, а иначе — логическое значение false
public virtual object Dequeue() Возвращает объект из начала вызывающей очереди. Возвращаемый объект удаляется из очереди
public virtual void Enqueue(object obj) Добавляет объект obj в конец очереди
public virtual object Peek() Возвращает объект из начала вызывающей очереди, но не удаляет его
public static Queue Synchronized(Queue queue) Возвращает синхронизированный вариант коллекции типа Queue, передаваемой в качестве параметра queue
public virtual object[] ToArray() Возвращает массив, который содержит копии элементов из вызывающей очереди
public virtual void TrimToSize() Устанавливает значение свойства Capacity равным значению свойства Count

В приведенном ниже примере программы демонстрируется применение класса Queue.

// Продемонстрировать применение класса Queue.
using System;
using System.Collections;
class QueueDemo {
    static void ShowEnq(Queue q, int a) {
        q.Enqueue(a);
        Console.WriteLine("Поместить в очередь: Enqueue(" + a + ")");

        Console.Write("Содержимое очереди: ");
        foreach(int i in q)
            Console.Write(i + " ");

        Console.WriteLine ();
    }

    static void ShowDeq(Queue q) {
        Console.Write("Извлечь из очереди: Dequeue -> ");
        int a = (int) q.Dequeue();
        Console.WriteLine(a);

        Console.Write("Содержимое очереди: ");
        foreach(int i in q)
            Console.Write(i + " ");

        Console.WriteLine();
    }

    static void Main() {
        Queue q = new Queue();

        foreach(int i in q)
            Console.Write(i + " ");

        Console.WriteLine();

        ShowEnq(q, 22);
        ShowEnq(q, 65);
        ShowEnq(q, 91);
        ShowDeq(q);
        ShowDeq(q);
        ShowDeq(q);

        try {
            ShowDeq (q);
        } catch (InvalidOperationException) {
            Console.WriteLine("Очередь пуста.");
        }
    }
}

Эта программа дает следующий результат.

Поместить в очередь: Enqueue(22)
Содержимое очереди: 22
Поместить в очередь: Enqueue(65)
Содержимое очереди: 22 65
Поместить в очередь: Enqueue(91)
Содержимое очереди: 22 65 91
Извлечь из очереди: Dequeue -> 22
Содержимое очереди: 65 91
Извлечь из очереди: Dequeue -> 65
Содержимое очереди: 91
Извлечь из очереди: Dequeue -> 91
Содержимое очереди:
Извлечь из очереди: Dequeue -> Очередь пуста.

Хранение отдельных битов в классе коллекции BitArray

Класс BitArray служит для хранения отдельных битов в коллекции. А поскольку в коллекции этого класса хранятся биты, а не объекты, то своими возможностями он отличается от классов других коллекций. Тем не менее в классе BitArray реализуют­ ся интерфейсы ICollection и IEnumerable как основополагающие элементы под­ держки всех типов коллекций. Кроме того, в классе BitArray реализуется интерфейс ICloneable.

В классе BitArray определено несколько конструкторов. Так, с помощью приве­ денного ниже конструктора можно сконструировать объект типа BitArray из массива логических значений.

public BitArray(bool[] values)

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

Коллекцию типа BitArray можно также составить из массива байтов, используя следующий конструктор.

public BitArray(byte[] bytes)

Здесь битами в коллекции становится уже целый их набор из массива bytes, при­ чем элемент bytes[0] обозначает первые 8 битов, элемент bytes[1] — вторые 8 би­ тов и т.д. Аналогично, коллекцию типа BitArray можно составить из массива цело­ численных значений, используя приведенный ниже конструктор.

public BitArray(int[ ] values)

В данном случае элемент values[0] обозначает первые 32 бита, элемент values[1] — вторые 32 бита и т.д.

С помощью следующего конструктора можно составить коллекцию типа BitArray, указав ее конкретный размер:

public BitArray(int length)

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

public BitArray(int length, bool defaultValue)

В данном случае все биты в коллекции инициализируются значением defaultValue, передаваемым конструктору в качестве параметра. И наконец, новую коллекцию типа BitArray можно создать из уже существую­ щей, используя следующий конструктор.

public BitArray(BitArray bits)

Вновь сконструированный объект будет содержать такое же количество битов, как и в указываемой коллекции bits, а в остальном это будут две совершенно разные кол­ лекции.

Коллекции типа BitArray подлежат индексированию. По каждому индексу указы­ вается отдельный бит в коллекции, причем нулевой индекс обозначает младший бит. В классе BitArray определяется ряд собственных методов, помимо тех, что уже объявлены в интерфейсах, которые в нем реализуются. Методы этого класса приведе­ ны в табл. 25.9. Обратите внимание на то, что в классе BitArray не поддерживается метод Synchronized(). Это означает, что для коллекций данного класса синхронизи­ рованная оболочка недоступна, а свойство IsSynchronized всегда имеет логическое значение false. Тем не менее для управления доступом к коллекции типа BitArray ее можно синхронизировать для объекта, предоставляемого упоминавшимся ранее свойством SyncRoot.

Таблица 25.9. Методы, определенные в классе BitArray

Метод Описание
public BitArray And(BitArray value) Выполняет операцию логического умножения И битов вызывающего объекта и коллекции value. Возвращает коллекцию типа BitArray, содержащую результат
public bool Get(int index) Возвращает значение бита, указываемого по индексу index
public BitArray Not() Выполняет операцию поразрядного логического отрицания НЕ битов вызывающей коллекции и возвращает коллекцию типа BitArray, содержащую результат
public BitArray Or(BitArray value) Выполняет операцию логического сложения ИЛИ битов вызывающего объекта и коллекции value. Возвращает коллекцию типа BitArray, содержащую результат
public void Set(int index, bool value) Устанавливает бит, указываемый по индексу Index, равным значению value
public void SetAll(bool value) Устанавливает все биты равными значению value
public BitArray Xor(BitArray value) Выполняет логическую операцию исключающее ИЛИ над битами вызывающего объекта и коллекции value. Возвращает коллекцию типа BitArray, содержащую результат

В классе BitArray определяется также собственное свойство, помимо тех, что ука­ заны в интерфейсах, которые в нем реализуются.

public int Length { get; set; }

Свойство Length позволяет установить или получить количество битов в коллек­ ции. Следовательно, оно возвращает такое же значение, как и стандартное свойство Count, определяемое для всех коллекций. В отличие от свойства Count, свойство Length доступно не только для чтения, но и для записи, а значит, с его помощью мож­ но изменить размер коллекции типа BitArray. Так, при сокращении коллекции типа BitArray лишние биты усекаются, начиная со старшего разряда. А при расширении коллекции типа BitArray дополнительные биты, имеющие логическое значение false, вводятся в коллекцию, начиная с того же старшего разряда.

Кроме того, в классе BitArray определяется следующий индексатор.

public bool this[int index] { get; set; }

С помощью этого индексатора можно получать или устанавливать значение элемента.

В приведенном ниже примере демонстрируется применение класса BitArray.

// Продемонстрировать применение класса BitArray.
using System;
using System.Collections;

class BADemo {

	public static void ShowBits(string rem,
						BitArray bits) {
		Console.WriteLine(rem);
		for(int i=0; i < bits.Count; i++)
			Console.Write("{0, -6} ", bits[i]);
		Console.WriteLine("\n");
	}

	static void Main() {
		BitArray ba = new BitArray(8);
		byte[] b = { 67 };
		BitArray ba2 = new BitArray(b);
		ShowBits("Исходное содержимое коллекции ba:", bа);

		ba = ba.Not();

		ShowBits("Содержимое коллекции bа после логической операции NOT:", ba);

		ShowBits("Содержимое коллекции bа2:", bа2);
		BitArray bа3 = bа.Хоr(bа2);

		ShowBits("Результат логической операции ba XOR bа2:", bаЗ);
	}
}

Эта программа дает следующий результат.

Исходное содержимое коллекции Ьа:
False False False False False False False False
Содержимое коллекции ba после логической операции NOT:
True True True True True True True True
Содержимое коллекции bа2:
True True False False False False ffrue False
Результат логической операции ba XOR ba2:
False False True True True True False True

Специальные коллекции

В среде .NET Framework предусмотрен ряд специальных коллекций, оптимизиро­ ванных для работы с данными конкретного типа иди для их обработки особым обра­ зом. Классы этих необобщенных коллекций определены в пространстве имен System. Collections.Specialized и перечислены ниже.

Класс специальной коллекции Описание
CollectionsUtil Содержит фабричные методы для создания коллекций
HybridDictionary Предназначен для коллекций, в которых для хранения небольшого количества пар “ключ-значение" используется класс ListDictionary. При превышении коллекцией определенного размера автоматически используется класс Hashtable для хранения ее элементов
ListDictionary Предназначен для коллекций, в которых для хранения пар “ключ-значение” используется связный список. Такие коллекции рекомендуются только для хранения небольшого количества элементов
NameValueCollection Предназначен для отсортированных коллекций, в которых хранятся пары “ключ-значение”, причем и ключ, и значение относятся к типу string
OrderedDictionary Предназначен для коллекций, в которых хранятся индексируемые пары “ключ-значение”
StringCollection Предназначен для коллекций, оптимизированных для хранения символьных строк
StringDictionary Предназначен для хеш-таблиц, в которых хранятся пары “ключ-значение”, причем и ключ, и значение относятся к типу string

Кроме того, в пространстве имен System.Collections определены три базовых абстрактных класса: CollectionBase, ReadOnlyCollectionBase и DictionaryBase. Эти классы могут наследоваться и служить в качестве отправной точки для разработки собственных специальных коллекций.

Обобщенные коллекции

Благодаря внедрению обобщений прикладной интерфейс Collections API значи­ тельно расширился, в результате чего количество классов коллекций и интерфей­ сов удвоилось. Обобщенные коллекции объявляются в пространстве имен System. Collections.Generic. Как правило, классы обобщенных коллекций являются не бо­ лее чем обобщенными эквивалентами рассматривавшихся ранее классов необобщен­ ных коллекций, хотя это соответствие не является взаимно однозначным. Например, в классе обобщенной коллекции LinkedList реализуется двунаправленный список, тогда как в необобщенном эквиваленте его не существует. В некоторых случаях одни и те же функции существуют параллельно в классах обобщенных и необобщенных кол­ лекций, хотя и под разными именами. Так, обобщенный вариант класса ArrayList называется List, а обобщенный вариант класса HashTable — Dictionary. Кроме того, конкретное содержимое различных интерфейсов и классов реорганизуется с ми­ нимальными изменениями для переноса некоторых функций из одного интерфейса в другой. Но в целом, имея ясное представление о необобщенных коллекциях, можно без особого труда научиться применять и обобщенные коллекции.

Как правило, обобщенные коллекции действуют по тому же принципу, что и не­ обобщенные, за исключением того, что обобщенные коллекции типизированы. Это означает, что в обобщенной коллекции можно хранить только те элементы, которые совместимы по типу с ее аргументом. Так, если требуется коллекция для хранения не­ связанных друг с другом разнотипных данных, то для этой цели следует использовать классы необобщенных коллекций. А во всех остальных случаях, когда в коллекции должны храниться объекты только одного типа, выбор рекомендуется останавливать на классах обобщенных коллекций.

Обобщенные коллекции определяются в ряде интерфейсов и классов, реализую­ щих эти интерфейсы. Все они описываются далее по порядку.

Интерфейсы обобщенных коллекций

В пространстве имен System.Collections.Generic определен целый ряд интер­ фейсов обобщенных коллекций, имеющих соответствующие аналоги среди интерфей­ сов необобщенных коллекций. Все эти интерфейсы сведены в табл. 25.10.

Таблица 25.10. Интерфейсы обобщенных коллекций

Интерфейс Описание
ICollection<T> Определяет основополагающие свойства обобщенных коллекций
IComparer<T> Определяет обобщенный метод Compare() для сравнения объектов, хранящихся в коллекции
IDictionary<Tkey, TValue> Определяет обобщенную коллекцию, состоящую из пар "ключ-значение’’
IEnumerable<T> Определяет обобщенный метод GetEnumerator(), предоставляющий перечислитель для любого класса коллекции
Enumerator<T> Предоставляет методы, позволяющие получать содержимое коллекции по очереди
IEqualityComparer<T> Сравнивает два объекта на предмет равенства
IList<T> Определяет обобщенную коллекцию, доступ к которой можно получить с помощью индексатора

Интерфейс ICollection

В интерфейсе ICollection<T> определен ряд свойств, которые являются общими для всех обобщенных коллекций. Интерфейс ICollection<T> является обобщенным вариантом необобщенного интерфейса ICollection, хотя между ними имеются не­ которые отличия.

Итак, в интерфейсе ICollection определены следующие свойства.

int Count { get; }
bool IsReadOnly { get; }

Свойство Count содержит ряд элементов, хранящихся в данный момент в коллек­ ции. А свойство IsReadOnly имеет логическое значение true, если коллекция доступ­ на только для чтения. Если же коллекция доступна как для чтения, так и для записи, то данное свойство имеет логическое значение false.

Кроме того, в интерфейсе ICollection<T> определены перечисленные ниже ме­ тоды. Обратите внимание на то, что в этом обобщенном интерфейсе определено не­ сколько большее количество методов, чем в его необобщенном аналоге. Некоторые из перечисленных выше методов генерируют исключе­ ние NotSupportedException, если коллекция доступна только для чтения.

Метод Описание
void Add(T item) Добавляет элемент item в вызывающую коллекцию. Генерирует исключение NotSupportedException, если коллекция доступна только для чтения
void Clear() Удаляет все элементы из вызывающей коллекции
bool Contains(T item) Возвращает логическое значение true, если вызывающая коллекция содержит элемент item, а иначе — логическое значение false
void CopyTo(T[] array, int arrayIndex) Копирует содержимое вызывающей коллекции в массив array, начиная с элемента, указываемого по индексу arrayIndex
void Remove(T item) Удаляет первое вхождение элемента item в вызывающей коллекции. Возвращает логическое значение true, если элемент item удален. А если этот элемент не найден в вызывающей коллекции, то возвращается логическое значение false

А поскольку интерфейс ICollection<T> наследует от интерфейсов IEnumerable и IEnumerable<T>, то он включает в себя также обобщенную и необобщенную формы метода GetEnumerator().

Благодаря тому что в интерфейсе ICollection<T> реализуется интерфейс IEnumerable<T>, в нем поддерживаются также методы расширения, определенные в классе Enumerable. Несмотря на то что методы расширения предназначены главным образом для поддержки LINQ, им можно найти и другое применение, в том числе и в коллекциях.

Интерфейс IList

В интерфейсе IList<T> определяется такое поведение обобщенной коллекции, которое позволяет осуществлять доступ к ее элементам по индексу с отсчетом от нуля. Этот интерфейс наследует от интерфейсов IEnumerable, IEnumerable<T> и ICollection<T> и поэтому является обобщенным вариантом необобщенного интер­ фейса IList. Методы, определенные в интерфейсе IList<T>, перечислены в табл. 25.11. В двух из этих методов предусматривается модификация коллекции. Если же коллекция доступна только для чтения или имеет фиксированный размер, то методы Insert() и RemoveAt() генерируют исключение NotSupportedException.

Таблица 25.11. Методы, определенные в интерфейсе IList

Метод Описание
int IndexOf(Т item) Возвращает индекс первого вхождения элемента item в вызывающей коллекции. Если элемент item не обнаружен, то метод возвращает значение -1
void Insert(int index, T item) Вставляет в вызывающую коллекцию элемент item по индексу index
void RemoveAtfint index) Удаляет из вызывающей коллекции элемент, расположенный по указанному индексу index

Кроме того, в интерфейсе IList<T> определяется индексатор

Т this[int index] { get; set; }

который устанавливает или возвращает значение элемента коллекции по указанному индексу index.

Интерфейс IDictionary<TKey, TValue>

В интерфейсе IDictionary<TKey, TValue> определяется такое поведение обоб­ щенной коллекции, которое позволяет преобразовать уникальные ключи в соответ­ ствующие значения. Это означает, что в данном интерфейсе определяется коллекция, в которой хранятся пары "ключ-значение". Интерфейс IDictionary<TKey, TValue> наследует от интерфейсов IEnumerable, IEnumerable<KeyValuePair<TKey, TValue>> и ICollection<KeyValuePair<TKey, TValue>> и поэтому является обобщенным вариантом необобщенного интерфейса IDictionary. Методы, объяв­ ленные в интерфейсе IDictionary<TKey, TValue>, приведены в табл. 25.12. Все эти методы генерируют исключение ArgumentNullException при попытке указать пу­ стой ключ.

Таблица 25.12. Методы, определенные в интерфейсе IDictionary<TKey, TValue>

Метод Описание
void Add(TKey key, TValue value) Добавляет в вызывающую коллекцию пару “ключ-значение”, определяемую параметрами key и value. Генерирует исключение ArgumentException, если ключ key уже находится в коллекции
bool Contains(TKey key) Возвращает логическое значение true, если вызывающая коллекция содержит элемент key в качестве ключа, а иначе — логическое значение false
bool Remove(TKey key) Удаляет из коллекции элемент, ключ которого равен значению key
bool TryGetValue(TKey key, out TValue value) Предпринимает попытку извлечь значение из коллекции по указанному ключу key и присвоить это значение переменной value. При удачном исходе операции возвращается логическое значение true, а иначе — логическое значение false. Если ключ key не найден, переменной value присваивается значение, выбираемое по умолчанию

Кроме того, в интерфейсе IDictionary<TKey, TValue> определены перечислен­ ные ниже свойства.

Свойство Описание
ICollection Keys { get; } Получает коллекцию ключей
ICollection Values { get; } Получает коллекцию значений

Следует иметь в виду, что ключи и значения, содержащиеся в коллекции, доступны отдельными списками с помощью свойств Keys и Values.

И наконец, в интерфейсе IDictionary<TKey, TValue> определяется следующий индексатор.

TValue this[TKey key] { get; set; }

Этот индексатор служит для получения и установки значения элемента коллекции, а также для добавления в коллекцию нового элемента. Следует, однако, иметь в виду, что в качестве индекса в данном случае служит ключ элемента, а не сам индекс.

Интерфейсы IEnumerable и IEnumerator

Интерфейсы IEnumerable<T> и IEnumerator<T> являются обобщенными эк­ вивалентами рассмотренных ранее необобщенных интерфейсов IEnumerable и IEnumerator. В них объявляются аналогичные методы и свойства, да и действуют они по тому же принципу. Разумеется, обобщенные интерфейсы оперируют данными только того типа, который указывается в аргументе типа.

В интерфейсе IEnumerable<T> метод GetEnumerator() объявляется следующим образом.

IEnumerator<T> GetEnumerator()

Этот метод возвращает перечислитель типа Т для коллекции. А это означает, что он возвращает типизированный перечислитель.

Кроме того, в интерфейсе IEnumerable<T> определяются два таких же метода, как и в необобщенном его варианте: MoveNext() и Reset(). В этом интерфейсе объявля­ ется также обобщенный вариант свойства Current.

Т Current { get; }

Это свойство возвращает ссылку типа Т на следующий объект. А это означает, что обобщенный вариант свойства Current является типизированным.

Но между интерфейсами IEnumerator и IEnumerator<T> имеется одно важное различие: интерфейс IEnumerator<T> наследует от интерфейса IDisposable, тогда как интерфейс IEnumerator не наследует от него. В интерфейсе IDisposable опреде­ ляется метод Dispose(), который служит для освобождения неуправляемых ресурсов.

ПРИМЕЧАНИЕ В интерфейсе IEnumerable<T> реализуется также необобщенный интерфейс IEnumerable. Это означает, что в нем поддерживается необобщенный вариант метода GetEnumerator(). Кроме того, в интерфейсе IEnumerable реализуется необобщен­ ный интерфейс IEnumerator, а следовательно, в нем поддерживаются необобщенные ва­ рианты свойства Current.

Интерфейс IComparer

Интерфейс IComparer<T> является обобщенным вариантом рассмотренного ранее интерфейса IComparer. Главное отличие между ними заключается в том, что интер­ фейс IComparer обеспечивает типовую безопасность. В нем обобщенный вариант метода Compare() объявляется следующим образом.

int Compare(Т х, Т у)

В этом методе сравниваются объекты х и у. Он возвращает положительное значе­ ние, если значение объекта х больше, чем у объекта у; отрицательное — если значение объекта х меньше, чем у объекта у; и нулевое значение — если сравниваемые значения равны.

Интерфейс IEqualityComparer

Интерфейс IEqualityComparer полностью соответствует своему необобщен­ ному аналогу EqualityComparer. В нем определяются два следующих метода.

bool Equals (Т х, Т у)
int GetHashCode(Т obj)

Метод Equals() должен возвратить логическое значение true, если значения объектов х и у равны. А метод GetHashCode() возвращает хеш-код для объекта obj. Если два сравниваемых объекта равны, то их хеш-коды также должны быть одинаковы.

Интерфейс ISet

Интерфейс ISet<T> был добавлен в версию 4.0 среды .NET Framework. Он опре­ деляет поведение обобщенной коллекции, реализующей ряд уникальных элементов. Этот интерфейс наследует от интерфейсов IEnumerable, IEnumerable<T>, а также ICollection<T>. В интерфейсе ISet<T> определен ряд методов, перечисленных в табл. 25.13. Обратите внимание на то, что параметры этих методов указываются как относящиеся к типу IEnumerable<T>. Это означает, что в качестве второго аргумен­ та методу можно передать нечто, отличающееся от объектов типа ISet Но чаще всего оба аргумента оказываются экземплярами объектов типа ISet<T>

Таблица 25.13. Методы, определенные в интерфейсе ISet<T>

Метод Описание
void ExceptWith(Ienumerable<T> other) Удаляет из вызывающего множества те элементы, которые содержатся в другом множестве other
void IntersectWith(IEnumerable<T> other) После вызова этого метода вызывающее множество содержит пересечение своих элементов с элементами другого множества other
bool IsProperSubsetOf(IEnumerable<T> other) Возвращает логическое значение true, если вызывающее множество является правильным подмножеством другого множества other, а иначе — логическое значение false
bool IsProperSupersetOf(IEnumerable<T> other) возвращает логическое значение true, если вызывающее множество является правильным надмножеством другого множества other, а иначе — логическое значение false
bool IsSubsetOf(IEnumerable<T> other) Возвращает логическое значение true, если вызывающее множество является подмножеством другого множества other, а иначе — логическое значение false
bool IsSupersetOf(IEnumerable<T> other) Возвращает логическое значение true, если вызывающее множество является надмножеством другого множества other, а иначе — логическое значение false
bool Overlaps(IEnumerable<T> other) Возвращает логическое значение true, если вызывающее множество и другое множество other содержат хотя бы один общий элемент, а иначе — логическое значение false
bool SetEquals(IEnumerable<T> other) Возвращает логическое значение true, если все элементы вызывающего множества и другого множества other оказываются общими, а иначе — логическое значение false. Порядок расположения элементов не имеет значения, а дублирующиеся элементы во другом множестве other игнорируются
void SymmetricExceptWith(IEnumerable<T> other) После вызова этого метода вызывающее множество будет содержать симметрическую разность своих элементов и элементов другого множества other
void UnionWith(IEnumerable<T> other) После вызова этого метода вызывающее множество будет содержать объединение своих элементов и элементов другого множества other

Структура KeyValuePair<TKey, TValue>

В пространстве имен System.Collections.Generic определена структура KeyValuePair<TKey, TValue>. Она служит для хранения ключа и его значения и применяется в классах обобщенных коллекций, в которых хранятся пары "ключ- значение", как, например, в классе Dictionary<TKey, TValue>. В этой структуре определяются два следующих свойства.

public TKey Key { get; };
public TValue Value { get; };

В этих свойствах хранятся ключ и значение соответствующего элемента коллекции. Для построения объекта типа KeyValuePair<TKey, TValue> служит конструктор:

public KeyValuePair(TKey key, TValue value)

где key обозначает ключ, a value — значение.

Классы обобщенных коллекций

Как упоминалось ранее, классы обобщенных коллекций по большей части соот­ ветствуют своим необобщенным аналогам, хотя в некоторых случаях они носят другие имена. Отличаются они также своей организацией и функциональными возможно­ стями. Классы обобщенных коллекций определяются в пространстве имен System. Collections.Generic. В табл. 25.14 приведены классы, рассматриваемые в этой гла­ ве. Эти классы составляют основу обобщенных коллекций.

Таблица 25.14. Основные классы обобщенных коллекций

Класс Описание
Dictionary<Tkey, TValue> Сохраняет пары “ключ-значение". Обеспечивает такие же функциональные возможности, как и необобщенный класс Hashtable
HashSet<T> Сохраняет ряд уникальных значений, используя хеш-таблицу
LinkedList<T> Сохраняет элементы в двунаправленном списке
List<T> Создает динамический массив. Обеспечивает такие же функциональные возможности, как и необобщенный класс ArrayList
Queue<T> Создает очередь. Обеспечивает такие же функциональные возможности, как и необобщенный класс Queue
SortedDictionary<TKey, TValue> Создает отсортированный список из пар “ключ-значение”
SortedList<TKey, TValue> Создает отсортированный список из пар "ключ-значение”. Обеспечивает такие же функциональные возможности, как и необобщенный класс SortedList
SortedSet<T> Создает отсортированное множество
Stack<T> Создает стек. Обеспечивает такие же функциональные возможности, как и необобщенный класс Stack

ПРИМЕЧАНИЕ В пространстве имен System.Collections.Generic находятся также следующие классы: класс SynchronizedCollection<T> синхронизированной коллекции на осно­ ве класса IList<T>; класс SynchronizedReadOnlyCollection<T>, доступной толь­ ко для чтения синхронизированной коллекции на основе класса lList<T>; абстрактный класс SynchronizedKeyCollection<K, Т>, служащий в качестве базового для клас­ са коллекции System.ServiceModel.UriSchemeKeyedCollection; а также класс KeyedByTypeCollection<T> коллекции, в которой в качестве ключей используются от­ дельные типы данных.

Класс List

В классе List<T> реализуется обобщенный динамический массив. Он ничем принципиально не отличается от класса необобщенной коллекции ArrayList. В этом классе реализуются интерфейсы ICollection, ICollection<T>, IList, IList<T>, IEnumerable и IEnumerable<T>. У класса List<T> имеются следующие конструкторы.

public List()
public List(IEnumerable<T> collection)
public List(int capacity)

Первый конструктор создает пустую коллекцию класса List с выбираемой по умолчанию первоначальной емкостью. Второй конструктор создает коллекцию типа List с количеством инициализируемых элементов, которое определяется параметром collection и равно первоначальной емкости массива. Третий конструктор создает коллекцию типа List, имеющую первоначальную емкость, задаваемую параметром capacity. В данном случае емкость обозначает размер базового массива, используе­ мого для хранения элементов коллекции. Емкость коллекции, создаваемой в виде ди­ намического массива, может увеличиваться автоматически по мере добавления в нее элементов.

В классе List<T> определяется ряд собственных методов, помимо тех, что уже объ­ явлены в интерфейсах, которые в нем реализуются. Некоторые из наиболее часто ис­ пользуемых методов этого класса перечислены в табл. 25.15.

Таблица 25.15. Наиболее часто используемые методы, определенные в классе List<T>

Метод Описание
public virtual void AddRange(Icollection collection) Добавляет элементы из коллекции collection в конец вызывающей коллекции типа ArrayList
public virtual int BinarySearch(T item) Выполняет поиск в вызывающей коллекции значения, задаваемого параметром item. Возвращает индекс совпавшего элемента. Если искомое значение не найдено, возвращается отрицательное значение. Вызывающий список должен быть отсортирован
public int BinarySearch(Т item, IComparer<T> comparer) Выполняет поиск в вызывающей коллекции значения, задаваемого параметром item, используя для сравнения указанный способ, определяемый параметром comparer. Возвращает индекс совпавшего элемента. Если искомое значение не найдено, возвращается отрицательное значение. Вызывающий список должен быть отсортирован
public int BinarySearch(int index, int count, T item, IComparer<T> comparer) Выполняет поиск в вызывающей коллекции значения, задаваемого параметром item, используя для сравнения указанный способ, определяемый параметром comparer. Поиск начинается с элемента, указываемого по индексу index, и включает количество элементов, определяемых параметром count. Метод возвращает индекс совпавшего элемента. Если искомое значение не найдено, возвращается отрицательное значение. Вызывающий список должен быть отсортирован
public List<T> GetRange(int index, int count) Возвращает часть вызывающей коллекции. Часть возвращаемой коллекции начинается с элемента, указываемого по индексу index, и включает количество элементов, задаваемое параметром count. Возвращаемый объект ссылается на те же элементы, что и вызывающий объект
public int IndexOf(T item) Возвращает индекс первого вхождения элемента item в вызывающей коллекции. Если искомый элемент не обнаружен, возвращается значение -1
public void InsertRange(int index, IEnumerable<T> collection) Вставляет элементы коллекции collection в вызывающую коллекцию, начиная с элемента, указываемого по индексу index
public int LastlndexOf(T item) Возвращает индекс последнего вхождения элемента item в вызывающей коллекции. Если искомый элемент не обнаружен, возвращается значение -1
public void RemoveRange(int index, int count) Удаляет часть вызывающей коллекции, начиная с элемента, указываемого по индексу index, и включая количество элементов, определяемое параметром count
public void Reverse() Располагает элементы вызывающей коллекции в обратном порядке
public void Reverse(int index, int count) Располагает в обратном порядке часть вызывающей коллекции, начиная с элемента, указываемого по индексу index, и включая количество элементов, определяемое параметром count
public void Sort() Сортирует вызывающую коллекцию по нарастающей

В классе List<T> определяется также собственное свойство Capacity, помимо тех, что уже объявлены в интерфейсах, которые в нем реализуются. Это свойство объявля­ ется следующим образом.

public int Capacity { get; set; }

Свойство Capacity позволяет установить и получить емкость вызывающей коллек­ ции в качестве динамического массива. Эта емкость равна количеству элементов, кото­ рые может содержать коллекция до ее вынужденного расширения. Такая коллекция расширяется автоматически, и поэтому задавать ее емкость вручную необязательно. Но из соображений эффективности это иногда можно сделать, если заранее известно количество элементов коллекции. Благодаря этому исключаются издержки на выделе­ ние дополнительной памяти.

В классе List<T> реализуется также приведенный ниже индексатор, определен­ ный в интерфейсе IList<T>.

public Т this[int index] { get; set; }

С помощью этого индексатора устанавливается и получается значение элемента коллекции, указываемое по индексу index.

В приведенном ниже примере программы демонстрируется применение клас­ са List<T>. Это измененный вариант примера, демонстрировавшего ранее класс ArrayList. Единственное изменение, которое потребовалось для этого, заключалось в замене класса ArrayList классом List, а также в использовании параметров обоб­ щенного типа.

Метод Описание
public void Sort(IСоmраrеr<Т> comparer) Сортирует вызывающую коллекцию, используя для сравнения способ, задаваемый параметром comparer. Если параметр comparer имеет пустое значение, то для сравнения используется способ, выбираемый по умолчанию
public void Sort(Comparison<T> comparison) Сортирует вызывающую коллекцию, используя для сравнения указанный делегат
public void Sort (int index, int count, IComparer<T> comparer) Сортирует вызывающую коллекцию, используя для сравнения способ, задаваемый параметром comparer. Сортировка начинается с элемента, указываемого по индексу index, и включает количество элементов, определяемых параметром count. Если параметр comparer имеет пустое значение, то для сравнения используется способ, выбираемый по умолчанию
public T[] ToArray() Возвращает массив, содержащий копии элементов вызывающего объекта
public void TrimExcess() Сокращает емкость вызывающей коллекции таким образом, чтобы она не превышала 10% от количества элементов, хранящихся в ней на данный момент
// Продемонстрировать применение класса List<T>.
using System;
using System.Collections.Generic;

class GenListDemo {
	static void Main() {
		// Создать коллекцию в виде динамического массива.
		List<char> lst = new List<char>();
		Console.WriteLine("Исходное количество элементов: " + lst.Count);
		Console.WriteLine();
		Console.WriteLine("Добавить 6 элементов");

		// Добавить элементы в динамический массив.
		lst.Add('С');
		lst.Add('А');
		lst.Add('Е');
		lst.Add('В');
		lst.Add('D');
		lst.Add('F');
		Console.WriteLine("Количество элементов: " + lst.Count);

		// Отобразить содержимое динамического массива,
		// используя индексирование массива.
		Console.Write("Текущее содержимое: ");
		for (int i=0; i < lst.Count; i++)
			Console.Write(lst[i] + " ");
		Console.WriteLine("\n");

		Console.WriteLine("Удалить 2 элемента ");

		// Удалить элементы из динамического массива.
		lst.Remove('F');
		lst.Remove('А');

		Console.WriteLine("Количество элементов: " + lst.Count);

		// Отобразить содержимое динамического массива, используя цикл foreach.
		Console.Write("Содержимое: ");
		foreach(char с in lst)
			Console.Write(с + " ");
		Console.WriteLine("\n");

		Console.WriteLine("Добавить еще 20 элементов");
		// Добавить количество элементов, достаточное для
		// принудительного расширения массива.
		for(int i=0; i < 20; i++)
			lst.Add((char)('a' + i));
		Console.WriteLine("Текущая емкость: " + lst.Capacity);
		Console.WriteLine("Количество элементов после добавления 20 новых: " +
						lst.Count);
		Console.Write("Содержимое: ");
		foreach(char с in lst)
			Console.Write(с + " ");
		Console.WriteLine("\n");

		// Изменить содержимое динамического массива,
		// используя индексирование массива.
		Console.WriteLine("Изменить три первых элемента");
		lst[0] = 'X';
		lst[1] = 'Y';
		lst[2] = 'Z';

		Console.Write("Содержимое: ");
		foreach(char с in lst)
			Console.Write(с + " ");
		Console.WriteLine();

		// Следующая строка кода недопустима из-за
		// нарушения безопасности обобщенного типа.
		// lst.Add(99); // Ошибка, поскольку это не тип char!
	}
}

Эта версия программы дает такой же результат, как и предыдущая.

Исходное количество элементов: 0

Добавить 6 элементов
Количество элементов: 6
Текущее содержимое: С А Е В D F

Удалить 2 элемента
Количество элементов: 4
Содержимое: С Е В D

Добавить еще 20 элементов
Текущая емкость: 32
Количество элементов после добавления 20 новых: 24
Содержимое: C E B D a b c d e f g h i j k l m n o p q r s t

Изменить три первых элемента
Содержимое: X Y Z D a b c d e f g h i j k l m n o p q r s t

Класс LinkedList

В классе LinkedList<T> создается коллекция в виде обобщенного двунаправлен­ ного списка. В этом классе реализуются интерфейсы ICollection, ICollection, IEnumerable, IEnumerable<T>, ISerializable и IDeserializationCallback. В двух последних интерфейсах поддерживается сериализация списка. В классе LinkedList<T> определяются два приведенных ниже открытых конструктора.

public LinkedList()
public LinkedList(IEnumerable<T> collection)

В первом конструкторе создается пустой связный список, а во втором конструкто­ ре — список, инициализируемый элементами из коллекции collection.

Как и в большинстве других реализаций связных списков, в классе LinkedList<T> инкапсулируются значения, хранящиеся в узлах списка, где находятся также ссылки на предыдущие и последующие элементы списка. Эти узлы представляют собой объекты класса LinkedListNode<T>. В классе LinkedListNode<T> предоставляются четыре следующих свойства.

public LinkedListNode<T> Next { get; }
public LinkedListNode<T> Previous { get; }
public LinkedList<T> List { get; }
public T Value { get; set; }

С помощью свойств Next и Previous получаются ссылки на предыдущий и после­ дующий узлы списка соответственно, что дает возможность обходить список в обоих направлениях. Если же предыдущий или последующий узел отсутствует, то возвра­ щается пустая ссылка. Для получения ссылки на сам список служит свойство List. А с помощью свойства Value можно устанавливать и получать значение, находящееся в узле списка.

В классе LinkedList<T> определяется немало методов. В табл. 25.16 приве­ дены наиболее часто используемые методы данного класса. Кроме того, в клас­ се LinkedList<T> определяются собственные свойства, помимо тех, что уже объявлены в интерфейсах, которые в нем реализуются. Эти свойства приведены ниже.

public LinkedListNode<T> First { get; }
public LinkedListNode<T> Last { get; }

С помощью свойства First получается первый узел в списке, а с помощью свой­ ства Last — последний узел в списке.

Таблица 25.16. Наиболее часто используемые методы, определенные в классе LinkedList<T>

Метод Описание
public LinkedListNode<T> AddAfter(LinkedListNode<T> node, T value) Добавляет в список узел со значением value непосредственно после указанного узла node. Указываемый узел node не должен быть пустым (null). Метод возвращает ссылку на узел, содержащий значение value
public void AddAfter(LinkedListNode<T> node, LinkedListNode<T> newNode) Добавляет в список новый узел newNode непосредственно после указанного узла node. Указываемый узел node не должен быть пустым (null). Если узел node отсутствует в списке или если новый узел newNode является частью другого списка, то генерируется исключение InvalidOperationException
public LinkedListNode<T> AddBefore(LinkedListNode<T> node, T value) Добавляет в список узел со значением value непосредственно перед указанным узлом node. Указываемый узел node не должен быть пустым (null). Метод возвращает ссылку на узел, содержащий значение value
public void AddBefore(LinkedListNode<T> node, LinkedListNode<T> newNode) Добавляет в список новый узел newNode непосредственно перед указанным узлом node. Указываемый узел node не должен быть пустым (null). Если узел node отсутствует в списке или если новый узел newNode является частью другого списка, то генерируется исключени InvalidOperationException
public LinkedList<T> AddFirst(T value) Добавляет узел со значением value в начало списка. Метод возвращает ссылку на узел, содержащий значение value
public void AddFirst(LinkedListNode node) Добавляет узел node в начало списка. Если узел node является частью другого списка, то генерируется исключение InvalidOperationException
public LinkedList<T> AddLast(T value) Добавляет узел со значением value в конец списка. Метод возвращает ссылку на узел, содержащий значение value
public void AddLast(LinkedListNode node) Добавляет узел node в конец списка. Если узел node является частью другого списка, то генерируется исключение InvalidOperationException
public LinkedList<T> Find(T value) Возвращает ссылку на первый узел в списке, имеющий значение value. Если искомое значение value отсутствует в списке, то возвращается пустое значение
public LinkedList<T> FindLast(T value) Возвращает ссылку на последний узел в списке, имеющий значение value. Если искомое значение value отсутствует в списке, то возвращается пустое значение
public bool Remove(T value) Удаляет из списка первый узел, содержащий значение value. Возвращает логическое значение true, если узел удален, т.е. если узел со значением value обнаружен в списке и удален; в противном случае возвращает логическое значение false
public void Remove(LinkedList<T> node) Удаляет из списка узел, соответствующий указанному узлу node. Если узел node отсутствует в списке, то генерируется исключение InvalidOperationException
public void RemoveFirst() Удаляет из списка первый узел
public void RemoveLast() Удаляет из списка последний узел

В приведенном ниже примере программы демонстрируется применение класса LinkedList<T>.

// Продемонстрировать применение класса LinkedList<T>.
using System;
using System.Collections.Generic;

class GenLinkedListDemo {
	static void Main() {
		// Создать связный список.
		LinkedList<char> ll = new LinkedList<char>();

		Console.WriteLine("Исходное количество элементов в списке: " + ll.Count)

		Console.WriteLine();

		Console.WriteLine("Добавить в список 5 элементов");

		// Добавить элементы в связный список.
		ll.AddFirst('А');
		ll.AddFirst('В');
		ll.AddFirst('С');
		ll.AddFirst('D');
		ll.AddFirst('Е');

		Console.WriteLine("Количество элементов в списке: " + ll.Count);

		// Отобразить связный список, обойдя его вручную.
		LinkedListNode<char> node;

		Console.Write("Отобразить содержимое списка по ссылкам: ");
		for(node = ll.First; node != null; node = node.Next)
			Console.Write(node.Value + " ");

		Console.WriteLine("\n");

		// Отобразить связный список, обойдя его в цикле foreach.
		Console.Write("Отобразить содержимое списка в цикле foreach: ");
		foreach(char ch in 11)
			Console.Write(ch + " ");

		Console.WriteLine("\n");

		// Отобразить связный список, обойдя его вручную в обратном направлении.
		Console.Write("Следовать по ссылкам в обратном направлении: ");
		for(node = ll.Last; node != null; node = node.Previous)
			Console.Write(node.Value + " ");

		Console.WriteLine("\n");

		// Удалить из списка два элемента.
		Console.WriteLine("Удалить 2 элемента из списка");

		// Удалить элементы из связного списка.
		ll.Remove('С');
		ll.Remove('А');

		Console.WriteLine("Количество элементов в списке: " + ll.Count);

		// Отобразить содержимое видоизмененного списка в цикле foreach.
		Console.Write("Содержимое списка после удаления элементов: ");
		foreach(char ch in ll)
			Console.Write(ch + " ");

		Console.WriteLine("\n");

		// Добавить три элемента в конец списка.
		ll.AddLast('X');
		ll.AddLast('Y');
		ll.AddLast('Z');

		Console.Write("Содержимое списка после ввода элементов: ");
		foreach(char ch in ll)
			Console.Write(ch + " ");

		Console.WriteLine("\n");
	}
}

Ниже приведен результат выполнения этой программы.

Исходное количество элементов в списке: 0

Добавить в список 5 элементов
Количество элементов в списке: 5
Отобразить содержимое списка по ссылкам: Е D С В А

Отобразить содержимое списка в цикле foreach: Е D С В А

Следовать по ссылкам в обратном направлении: А В С D Е

Удалить 2 элемента из списка
Количество элементов в списке: 3
Содержимое списка после удаления элементов: Е D В

Содержимое списка после ввода элементов: Е D В X Y Z

Самое примечательное в этой программе — это обход списка в прямом и обрат­ ном направлении, следуя по ссылкам, предоставляемым свойствами Next и Previous. Двунаправленный характер подобных связных списков имеет особое значение для приложений, управляющих базами данных, где нередко требуется перемещаться по списку в обоих направлениях.

Класс Dictionary<TKey, TValue>

Класс Dictionary<TKey, TValue> позволяет хранить пары "ключ-значение" в коллекции как в словаре. Значения доступны в словаре по соответствующим клю­ чам. В этом отношении данный класс аналогичен необобщенному классу Hashtable. В классе Dictionary<TKey, TValue> реализуются интерфейсы IDictionary, IDictionary<TKey, TValue>, ICollection, ICollection<KeyValuePair<TKey, TValue>>, IEnumerable, IEnumerable<KeyValuePair<TKey, TValue>>, ISerializable и IDeserializationCallback. В двух последних интерфейсах поддер­ живается сериализация списка. Словари имеют динамический характер, расширяясь по мере необходимости.

В классе Dictionary<TKey, TValue> предоставляется немало конструкторов. Ниже перечислены наиболее часто используемые из них.

public Dictionary()
public Dictionary(IDictionary<TKey, TValue> dictionary)
public Dictionary(int capacity)

В первом конструкторе создается пустой словарь с выбираемой по умолчанию первоначальной емкостью. Во втором конструкторе создается словарь с указанным ко­ личеством элементов dictionary. А в третьем конструкторе с помощью параметра сараcity указывается емкость коллекции, создаваемой в виде словаря. Если размер словаря заранее известен, то, указав емкость создаваемой коллекции, можно исклю­ чить изменение размера словаря во время выполнения, что, как правило, требует до­ полнительных затрат вычислительных ресурсов.

В классе Dictionary<TKey, TValue> определяется также ряд методов. Некото­ рые наиболее часто используемые методы этого класса сведены в табл. 25.17.

Таблица 25.17. Наиболее часто используемые методы, определенные в классе Dictionary<TKey, TValue>

Метод Описание
public void Add(TKey key, TValue value) Добавляет в словарь пару “ключ-значение”, определяемую параметрами key и value. Если ключ key уже находится в словаре, то его значение не изменяется, и генерируется исключение ArgumentException
public bool ContainsKey(TKey key) Возвращает логическое значение true, если вызывающий словарь содержит объект key в качестве ключа; а иначе — логическое значение false
public bool ContainsValue(TValue value) Возвращает логическое значение true, если вызывающий словарь содержит значение value; в противном случае — логическое значение false
public bool Remove(TKey key) Удаляет ключ key из словаря. При удачном исходе операции возвращается логическое значение true, а если ключ key отсутствует в словаре — логическое значение false

Кроме того, в классе Dictionary<TKey, TValue> определяются собственные свойства, помимо тех, что уже объявлены в интерфейсах, которые в нем реализуются.

Эти свойства приведены ниже.

Свойство Описание
public IEqualityComparer<TKey> Comparer { get; } Получает метод сравнения для вызывающего словаря
public Dictionary<TKey, TValue>.KeyCollection Keys { get; } Получает коллекцию ключей
public Dictionary<TKey, TValue>.ValueCollection Values { get; } Получает коллекцию значений

Следует иметь в виду, что ключи и значения, содержащиеся в коллекции, доступны отдельными списками с помощью свойств Keys и Values. В коллекциях типа Dictionary<TKey, TValue>.KeyCollection и Dictionary<TKey, TValue>.

ValueCollection реализуются как обобщенные, так и необобщенные формы интер­ фейсов ICoirection и IEnumerable.

И наконец, в классе Dictionary<TKey, TValue> реализуется приведенный ниже индексатор, определенный в интерфейсе IDictionary<TKey, TValue>.

public TValue this[TKey key] { get; set; }

Этот индексатор служит для получения и установки значения элемента коллекции, а также для добавления в коллекцию нового элемента. Но в качестве индекса в данном случае служит ключ элемента, а не сам индекс.

При перечислении коллекции типа Dictionary<TKey, TValue> из нее возвра­ щаются пары "ключ-значение" в форме структуры KeyValuePair<TKey, TValue>.

Напомним, что в этой структуре определяются два поля.

public TKey Key;
public TValue Value;

В этих полях содержится ключ или значение соответствующего элемента коллек­ ции. Как правило, структура KeyValuePair<TKey, TValue> не используется не­ посредственно, поскольку средства класса Dictionary<TKey, TValue> позволяют работать с ключами и значениями по отдельности. Но при перечислении коллекции типа Dictionary<TKey, TValue>, например, в цикле foreach перечисляемыми объектами являются пары типа KeyValuePair.

Все ключи в коллекции типа Dictionary<TKey, TValue> должны быть уни­ кальными, причем ключ не должен изменяться до тех пор, пока он служит в качестве ключа. В то же время значения не обязательно должны быть уникальными. К тому же объекты не хранятся в коллекции типа Dictionary<TKey, TValue> в отсортирован­ ном порядке.

В приведенном ниже примере демонстрируется применение класса Dictionary<TKey, TValue>.

// Продемонстрировать применение класса обобщенной
// коллекции Dictionary<TKey, TValue>.
using System;
using System.Collections.Generic;

class GenDictionaryDemo {
	static void Main() {
		// Создать словарь для хранения имен и фамилий
		// работников и их зарплаты.
		Dictionary<string, double> dict =
			new Dictionary<string, double>();

		// Добавить элементы в коллекцию.
		diet.Add("Батлер, Джон", 73000);
		diet.Add("Шварц, Capa", 59000);
		diet.Add("Пайк, Томас", 45000);
		diet.Add("Фрэнк, Эд", 99000);

		// Получить коллекцию ключей, т.е. фамилий и имен.
		ICollection<string> с = diet.Keys;

		// Использовать ключи для получения значений, т.е. зарплаты.
		foreach(string str in с)
			Console.WriteLine("{0}, зарплата: {1:C}", str, diet[str]);
	}
}

Ниже приведен результат выполнения этой программы.

Батлер, Джон, зарплата: $73,000.00
Шварц, Сара, зарплата: $59,000.00
Пайк, Томас, зарплата: $45,000.00
Фрэнк, Эд, зарплата: $99,000.00

Класс SortedDictionary<TKey, TValue>

В коллекции класса SortedDictionary<TKey, TValue> пары "ключ-значение" хранятся таким же образом, как и в коллекции класса Dictionary<TKey, TValue>, за исключением того, что они отсортированы по соответствующему ключу. В клас­ се SortedDictionary<TKey, TValue> реализуются интерфейсы IDictionary, IDictionary<TKey, TValue>, ICollection, ICollection<KeyValuePair<TKey, TValue>>, IEnumerable и IEnumerable<KeyValuePair<TKey, TValue>>. В классе SortedDictionary<TKey, TValue> предоставляются также следующие конструкторы.

public SortedDictionary()
public SortedDictionary(IDictionary<TKey, TValue> dictionary)
public SortedDictionary(IComparer<TKey> comparer)
public SortedDictionary(IDictionary<TKey, TValue> dictionary,
	IComparer<TKey> comparer)

В первом конструкторе создается пустой словарь, во втором конструкторе — сло­ варь с указанным количеством элементов dictionary. В третьем конструкторе допу­ скается указывать с помощью параметра comparer типа IComparer способ сравне­ ния, используемый для сортировки, а в четвертом конструкторе — инициализировать словарь, помимо указания способа сравнения.

В классе SortedDictionary<TKey, TValue> определен ряд методов. Некоторые наиболее часто используемые методы этого класса сведены в табл. 25.18. Таблица 25.18. Наиболее часто используемые методы, определенные в классе SortedDictionary<TKey, TValue>

Метод Описание public void Add(TKey key, TValue value) Добавляет в словарь пару "ключ-значение”, определяемую параметрами key и value. Если ключ key уже находится в словаре, то его значение не изменяется, и генерируется исключение ArgumentException public bool ContainsKey(TKey key) Возвращает логическое значение true, если вызывющий словарь содержит объект key в качестве ключа; в противном случае — логическое значение false public bool ContainsValue(TValue value) Возвращает логическое значение true, если вызывающий словарь содержит значение value; в противном случае — логическое значение false public bool Remove(TKey key) Удаляет ключ key из словаря. При удачном исходе операции возвращается логическое значение true, а если ключ key отсутствует в словаре — логическое значение false public Icomparer<TKey> Comparer { get; } Получает метод сравнения для вызывающего словаря public SortedDictionary<TKey, TValue>.KeyCollection Keys { get; } Получает коллекцию ключей public SortedDictionary<TKey, TValue>.ValueCollection Values { get; } Получает коллекцию значений

Кроме того, в классе SortedDictionary<TKey, TValue> определяются собствен­ ные свойства, помимо тех, что уже объявлены в интерфейсах, которые в нем реализу­ ются. Эти свойства приведены ниже.

Следует иметь в виду, что ключи и значения, содержащиеся в коллекции, доступ­ ны отдельными списками с помощью свойств Keys и Values. В коллекциях типа SortedDictionary<TKey, TValue>.KeyCollection и SortedDictionary<TKey, TValue>.ValueCollection реализуются как обобщенные, так и необобщенные фор­ мы интерфейсов ICollection и IEnumerable.

И наконец, в классе SortedDictionary<TKey, TValue> реализуется приведен­ ный ниже индексатор, определенный в интерфейсе IDictionary<TKey, TValue>.

public TValue this[TKey key] { get; set; }

Этот индексатор служит для получения и установки значения элемента коллекции, а также для добавления в коллекцию нового элемента. Но в данном случае в качестве индекса служит ключ элемента, а не сам индекс.

При перечислении коллекции типа SortedDictionary<TKey, TValue> из нее возвращаются пары "ключ-значение" в форме структуры KeyValuePair<TKey, TValue>. Напомним, что в этой структуре определяются два следующих поля.

public TKey Key;
public TValue Value;

В этих полях содержится ключ или значение соответствующего элемента коллек­ ции. Как правило, структура KeyValuePair<TKey, TValue> не используется не­ посредственно, поскольку средства класса SortedDictionary<TKey, TValue> по­ зволяют работать с ключами и значениями по отдельности. Но при перечислении коллекции типа SortedDictionary<TKey, TValue>, например в цикле foreach, перечисляемыми объектами являются пары типа KeyValuePair.

Все ключи в коллекции типа SortedDictionary<TKey, TValue> должны быть уникальными, причем ключ не должен изменяться до тех пор, пока он служит в каче­ стве ключа. В то же время значения не обязательно должны быть уникальными. В приведенном ниже примере демонстрируется применение класса SortedDictionary<TKey, TValue>. Это измененный вариант предыдущего приме­ ра, демонстрировавшего применение класса Dictionary<TKey, TValue>. В данном варианте база данных работников отсортирована по фамилии и имени работника, ко­ торые служат в качестве ключа.

// Продемонстрировать применение класса обобщенной
// коллекции SortedDictionary<TKey, TValue>.
using System;
using System.Collections.Generic;

class GenSortedDictionaryDemo {
	static void Main() {
	// Создать словарь для хранения имен и фамилий
	// работников и их зарплаты.
	SortedDictionary<string, double> dict =
		new SortedDictionary<string, double>();

	// Добавить элементы в коллекцию.
	dict.Add("Батлер, Джон", 73000);
	dict.Add("Шварц, Capa", 59000);
	dict.Add("Пайк, Томас", 45000);
	dict.Add("Фрэнк, Эд", 99000);

	// Получить коллекцию ключей, т.е. фамилий и имен.
	ICollection<string> с = dict.Keys;

	// Использовать ключи для получения значений, т.е. зарплаты.
	foreach(string str in с)
		Console.WriteLine("{0}, зарплата: {1:C}", str, diet[str]);
	}
}

Эта программа дает следующий результат.

Батлер, Джон, зарплата: $73,000.00
Пайк, Томас, зарплата: $45,000.00
Фрэнк, Эд, зарплата: $99,000.00
Шварц, Сара, зарплата: $59,000.00

Как видите, список работников и их зарплаты отсортированы по ключу, в качестве которого в данном случае служит фамилия и имя работника.

Класс SortedList<TKey, TValue>

В коллекции класса SortedList<TKey, TValue> хранится отсортированный спи­ сок пар "ключ-значение". Это обобщенный эквивалент класса необобщенной коллекции SortedList. В классе SortedList<TKey, TValue> реализуются интерфейсы IDictionary, IDictionary<TKey, TValue>, ICollection, ICollection<KeyValuePair<TKey, TValue>>, IEnumerable и IEnumerable<KeyValuePair<TKey, TValue>>. Размер коллекции типа SortedList<TKey, TValue> изменяется динамически, автоматически увеличиваясь по мере необходимости. Класс SortedList<TKey, TValue> подобен классу SortedDictionary<TKey, TValue>, но у него другие рабочие характеристики. В част­ ности, класс SortedList<TKey, TValue> использует меньше памяти, тогда как класс SortedDictionary<TKey, TValue> позволяет быстрее вставлять неупорядоченные эле­ менты в коллекцию.

В классе SortedList<TKey, TValue> предоставляется немало конструкторов. Ниже перечислены наиболее часто используемые конструкторы этого класса.

public SortedList()
public SortedList(IDictionary<TKey, TValue> dictionary)
public SortedList(int capacity)
public SortedList(IComparer<TK> comparer)

В первой форме конструктора создается пустой список с выбираемой по умолча­ нию первоначальной емкостью. Во второй форме конструктора создается отсортиро­ ванный список с указанным количеством элементов dictionary. В третьей форме конструктора с помощью параметра capacity задается емкость коллекции, созда­ ваемой в виде отсортированного списка. Если размер списка заранее известен, то, ука­ зав емкость создаваемой коллекции, можно исключить изменение размера списка во время выполнения, что, как правило, требует дополнительных затрат вычислительных ресурсов. И в четвертой форме конструктора допускается указывать с помощью пара­ метра comparer способ сравнения объектов, содержащихся в списке.

Емкость коллекции типа SortedList<TKey, TValue> увеличивается автоматиче­ ски по мере необходимости, когда в список добавляются новые элементы. Если теку­ щая емкость коллекции превышается, то она увеличивается. Преимущество указания емкости коллекции типа SortedList<TKey, TValue> при ее создании заключает­ ся в снижении или полном исключении издержек на изменение размера коллекции. Разумеется, указывать емкость коллекции целесообразно лишь в том случае, если за­ ранее известно, сколько элементов требуется хранить в ней.

В классе SortedList<TKey, TValue> определяется ряд собственных методов, по­ мимо тех, что уже объявлены в интерфейсах, которые в нем реализуются. Некоторые из наиболее часто используемых методов этого класса перечислены в табл. 25.19. Сле­ дует иметь в виду, что перечислитель, возвращаемый методом GetEnumerator(), слу­ жит для перечисления пар "ключ-значение", хранящихся в отсортированном списке в виде объектов типа KeyValuePair.

Таблица 25.19. Наиболее часто используемые методы, определенные в классе SortedList<TKey, TValue>

Метод Описание
public void Add(TKey key, TValue value) Добавляет в список пару "ключ-значение”, определяемую параметрами key и value. Если ключ key уже находится в списке, то его значение не изменяется, и генерируется исключение ArgumentException
public bool ContainsKey(TK key) Возвращает логическое значение true, если вызывающий список содержит объект key в качестве ключа; а иначе — логическое значение false
public bool ContainsValue(TValue value) Возвращает логическое значение true, если вызывающий список содержит значение value; в противном случае — логическое значение false
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() Возвращает перечислитель для вызывающего словаря
public int IndexOfKey(TKey key) Возвращает индекс ключа key. Если искомый ключ не обнаружен в списке, возвращается значение -1
public int IndexOfValue(TValue value) Возвращает индекс первого вхождения значения value в вызывающем списке. Если искомое значение не обнаружено в списке, возвращается значение -1
public bool Remove(TKey key) Удаляет из списка пару “ключ-значение” по указанному ключу key. При удачном исходе операции возвращается логическое значение true, а если ключ key отсутствует в списке — логическое значение false
public void RemoveAt(int index) Удаляет из списка пару “ключ-значение" по указанному индексу index
public void TrimExcess() Сокращает избыточную емкость вызывающей коллекции в виде отсортированного списка

Кроме того, в классе SortedList<TK, TV> определяются собственные свойства, помимо тех, что уже объявлены в интерфейсах, которые в нем реализуются. Эти свой­ ства приведены ниже.

Свойство Описание
public int Capacity { get; set; } Получает или устанавливает емкость вызывающей коллекции в виде отсортированного списка
public IComparer<TK> Comparer { get; } Получает метод сравнения для вызывающего списка
public IList<TK> Keys { get; } Получает коллекцию ключей
public IList<TV> Values { get; } Получает коллекцию значений ранее примера базы данных работников. В данном варианте база данных хранится в коллекции типа SortedList.

И наконец, в классе SortedList<TKey, TValue> реализуется приведенный ниже индексатор, определенный в интерфейсе IDictionary<TKey, TValue>

public TValue this[TKey key] { get; set; }

Этот индексатор служит для получения и установки значения элемента коллекции, а также для добавления в коллекцию нового элемента. Но в данном случае в качестве индекса служит ключ элемента, а не сам индекс.

В приведенном ниже примере демонстрируется применение класса SortedList<TKey, TValue>. Это еще один измененный вариант представленного

// Продемонстрировать применение класса обобщенной
// коллекции SortedList<TKey, TValue>.
using System;
using System.Collections.Generic;

class GenSLDemo {
    static void Main() {
        // Создать коллекцию в виде отсортированного списка
        // для хранения имен и фамилий работников и их зарплаты.
        SortedList<string, double> sl =
            new SortedList<string, double>();

        // Добавить элементы в коллекцию.
        sl.Add("Батлер, Джон", 73000);
        sl.Add("Шварц, Capa", 59000);
        sl.Add("Пайк, Томас", 45000);
        sl.Add("Фрэнк, Эд", 99000);

        // Получить коллекцию ключей, т.е. фамилий и имен.
        ICollection<string> с = sl.Keys;

        // Использовать ключи для получения значений, т.е. зарплаты.
        foreach(string str in с)
            Console.WriteLine("{0}, зарплата: {1:C}", str, sl[str]);
        Console.WriteLine();
    }
}

Ниже приведен результат выполнения этой программы.

Батлер, Джон, зарплата: $73,000.00
Пайк, Томас, зарплата: $45,000.00
Фрэнк, Эд, зарплата: $99,000.00
Шварц, Сара, зарплата: $59,000.00

Как видите, список работников и их зарплаты отсортированы по ключу, в качестве которого в данном случае служит фамилия и имя работника.

Класс Stack

Класс Stack<T> является обобщенным эквивалентом класса необобщенной кол­ лекции Stack. В нем поддерживается стек в виде списка, действующего по принципу "первым пришел — последним обслужен". В этом классе реализуются интерфейсы Collection, IEnumerable и IEnumerable<T>. Кроме того, в классе Stack<T> непо­ средственно реализуются методы Clear(), Contains() и СоруТо(), определенные в интерфейсе ICollection<T>. А методы Add() и Remove() в этом классе не поддер­ живаются, как, впрочем, и свойство IsReadOnly. Коллекция класса Stack<T> имеет динамический характер, расширяясь по мере необходимости, чтобы вместить все эле­ менты, которые должны в ней храниться. В классе Stack<T> определяются следующие конструкторы.

public Stack()
public Stack(int capacity)
public Stack(IEnumerable<T> collection)

В первой форме конструктора создается пустой стек с выбираемой по умолчанию первоначальной емкостью, а во второй форме — пустой стек, первоначальный размер которого определяет параметр сараcity. И в третьей форме создается стек, содержа­ щий элементы коллекции, определяемой параметром collection. Его первоначаль­ ная емкость равна количеству указанных элементов.

В классе Stack<T> определяется ряд собственных методов, помимо тех, что уже объявлены в интерфейсах, которые в нем реализуются, а также в интерфейсе ICollection<T>. Некоторые из наиболее часто используемых методов этого клас­ са перечислены в табл. 25.20. Как и в классе Stack, эти методы обычно применяются следующим образом. Для того чтобы поместить объект на вершине стека, вызывается метод Push(). А для того чтобы извлечь и удалить объект из вершины стека, вызыва­ ется метод Pop(). Если же объект требуется только извлечь, но не удалить из вершины стека, то вызывается метод Рееk(). А если вызвать метод Pop() или Рееk(), когда вы­ зывающий стек пуст, то сгенерируется исключение InvalidOperationException.

Таблица 25.20. Методы, определенные в классе Stack<T>

Метод Описание
public T Peek() Возвращает элемент, находящийся на вершине стека, но не удаляет его
public T Pop() Возвращает элемент, находящийся на вершине стека, удаляя его в процессе работы
public void Push(T item) Помещает элемент item в стек
public T[] ToArray() Возвращает массив, содержащий копии элементов вызывающего стека
public void TrimExcess() Сокращает избыточную емкость вызывающей коллекции в виде стека

В приведенном ниже примере программы демонстрируется применение класса Stack<T>.

// Продемонстрировать применение класса Stack<T>.
using System;
using System.Collections.Generic;

class GenStackDemo {
    static void Main() {
        Stack<string> st = new Stack<string>();

        st.Push("один");
        st.Push("два");
        st.Push("три");
        st.Push("четыре");
        st.Push("пять");

        while(st.Count > 0) {
            string str = st.Pop();
            Console.Write(str + " ");
        }
        Console.WriteLine();
    }
}

При выполнении этой программы получается следующий результат.

пять четыре три два один

Класс Queue

Класс Queue<T> является обобщенным эквивалентом класса необобщенной кол­ лекции Queue. В нем поддерживается очередь в виде списка, действующего по прин­ ципу "первым пришел — первым обслужен". В этом классе реализуются интерфейсы ICollection, IEnumerable и IEnumerable<T>. Кроме того, в классе Queue<T> не­ посредственно реализуются методы Clear(), Contains() и CopyTo(), определен­ ные в интерфейсе ICollection<T>. А методы Add() и Remove() в этом классе не поддерживаются, как, впрочем, и свойство IsReadOnly. Коллекция класса Queue имеет динамический характер, расширяясь по мере необходимости, чтобы вместить все элементы, которые должны храниться в ней. В классе Queue<T> определяются сле­ дующие конструкторы.

public Queue()
public Queue(int capacity)
public Queue(IEnumerable<T> collection)

В первой форме конструктора создается пустая очередь с выбираемой по умолча­ нию первоначальной емкостью, а во второй форме — пустая очередь, первоначальный размер которой определяет параметр capacity. И в третьей форме создается очередь, содержащая элементы коллекции, определяемой параметром collection. Ее перво­ начальная емкость равна количеству указанных элементов.

В классе Queue<T> определяется ряд собственных методов, помимо тех, что уже объявлены в интерфейсах, которые в нем реализуются, а также в интерфей­ се ICollection<T>. Некоторые из наиболее часто используемых методов этого класса перечислены в табл. 25.21. Как и в классе Queue, эти методы обычно приме­ няются следующим образом. Для того чтобы поместить объект в очередь, вызыва­ ется метод Enqueue(). Если требуется извлечь и удалить первый объект из начала очереди, то вызывается метод Dequeue(). Если же требуется извлечь, но не уда­ лять следующий объект из очереди, то вызывается метод Рееk(). А если методы Dequeue() и Рееk() вызываются, когда очередь пуста, то генерируется исключение InvalidOperationException.

Таблица 25.21. Методы, определенные в классе Queue

Метод Описание
public Т Dequeue() Возвращает объект из начала вызывающей очереди. Возвращаемый объект удаляется из очереди
public void Enqueue(Т item) Добавляет элемент item в конец очереди
public T Peek() Возвращает элемент из начала вызывающей очереди, но не удаляет его
public virtual Т[] ToArray() Возвращает массив, который содержит копии элементов из вызывающей очереди
public void TrimExcess() Сокращает избыточную емкость вызывающей коллекции в виде очереди

В приведенном ниже примере демонстрируется применение класса Queue<T>.

// Продемонстрировать применение класса Queue<T>.
using System;
using System.Collections.Generic;

class GenQueueDemo {
	static void Main() {
		Queue<double> q = new Queue<double>();

		q.Enqueue(98.6);
		q.Enqueue(212.0);
		q.Enqueue(32.0);
		q.Enqueue(3.1416);

		double sum = 0.0;
		Console.Write("Очередь содержит: ");
		while(q.Count > 0) {
			double val = q. Dequeued;
			Console.Write(val + " ");
			sum += val;
		}

		Console.WriteLine("\nИтоговая сумма равна " + sum);
	}
}

Вот к какому результату приводит выполнение этой программы.

Очередь содержит: 98.6 212 32 3.1416
Итоговая сумма равна 345.7416

Класс HashSet

В классе HashSet<T> поддерживается коллекция, реализующая множество. Для хранения элементов этого множества в нем используется хеш-таблица. В клас­ се HashSet<T> реализуются интерфейсы ICollection<T>, ISet<T>, IEnumerable, IEnumerable<T>, ISerializable, а также IDeserializationCallback. В коллек­ ции типа HashSet<T> реализуется множество, все элементы которого являются уни­ кальными. Иными словами, дубликаты в таком множестве не допускаются. Порядок следования элементов во множестве не указывается. В классе HashSet<T> определяет­ ся полный набор операций с множеством, определенных в интерфейсе ISet<T>, вклю­ чая пересечение, объединение и разноименность. Благодаря этому класс HashSet<T> оказывается идеальным средством для работы с множествами объектов, когда порядок расположения элементов во множестве особого значения не имеет. Коллекция типа HashSet<T> имеет динамический характер и расширяется по мере необходимости, чтобы вместить все элементы, которые должны в ней храниться.

Ниже перечислены наиболее употребительные конструкторы, определенные в классе HashSet<T>.

public HashSet()
public HashSet(IEnumerable<T> collection)
public HashSet(IEqualityCompare comparer)
public HashSet(IEnumerable<T> collection, IEqualityCompare comparer)

В первой форме конструктора создается пустое множество, а во второй форме — множество, состоящее из элементов указываемой коллекции collection. В третьей форме конструктора допускается указывать способ сравнения с помощью параметра comparer. А в четвертой форме создается множество, состоящее из элементов указы­ ваемой коллекции collection, и используется заданный способ сравнения comparer. Имеется также пятая форма конструктора данного класса, в которой допускается ини­ циализировать множество последовательно упорядоченными данными.

В классе HashSet<T> реализуется интерфейс ISet<T>, а следовательно, в нем пре­ доставляется полный набор операций со множествами. В этом классе предоставляется также метод RemoveWhere(), удаляющий из множества элементы, не удовлетворяю­ щие заданному условию, или предикату.

Помимо свойств, определенных в интерфейсах, которые реализуются в классе HashSet<T>, в него введено дополнительное свойство Comparer, приведенное ниже.

public IEqualityComparer<T> Comparer { get; }

Оно позволяет получать метод сравнения для вызывающего хеш-множества.

Ниже приведен конкретный пример применения класса HashSet<T>.

// Продемонстрировать применение класса HashSet<T>.
using System;
using System.Collections.Generic;

class HashSetDemo {
	static void Show(string msg, HashSet<char> set) {
		Console.Write(msg);
		foreach(char ch in set)
		Console.Write(ch + " ");
		Console.WriteLine();
	}

	static void Main() {
		HashSet<char> setA = new HashSet<char>();
		HashSet<char> setB = new HashSet<char>();

		setA.Add('A');
		setA.Add('B');
		setA.Add('С');

		setB.Add('C');
		setB.Add('D');
		setB.Add('Е');

		Show("Исходное содержимое множества setA: ", setA);
		Show("Исходное содержимое множества setB: ", setB);

		setA.SymmetricExceptWith(setB);
		Show("Содержимое множества setA после " +
			"разноименности со множеством SetB: ", setA);

		setA.UnionWith(setB);
		Show("Содержимое множества setA после " +
			"объединения со множеством SetB: ", setA);

		setA.ExceptWith(setB);
		Show("Содержимое множества setA после " +
			"вычитания из множества setB: ", setA);

		Console.WriteLine();
	}
}

Ниже приведен результат выполнения программы из данного примера.

Исходное содержимое множества setA: A B C
Исходное содержимое множества setB: С D Е
Содержимое множества setA после разноименности со множеством SetB: А В D Е
Содержимое множества setA после объединения со множеством SetB: А В D Е С
Содержимое множества setA после вычитания из множества setB: А В

Класс SortedSet

Класс SortedSet<T> представляет собой новую разновидность коллекции, введенную в версию 4.0 среды .NET Framework. В нем поддерживается коллек­ ция, реализующая отсортированное множество. В классе SortedSet<T> реали­ зуются интерфейсы ISet<T>, ICollection, ICollection<T>, IEnumerable, IEnumerable<T>, ISerializable, а также IDeserializationCallback. В коллек­ ции типа SortedSet<T> реализуется множество, все элементы которого являются уникальными. Иными словами, дубликаты в таком множестве не допускаются. В клас­ се SortedSet<T> определяется полный набор операций с множеством, определенных в интерфейсе ISet<T>, включая пересечение, объединение и разноименность. Благо­ даря тому что все элементы коллекции типа SortedSet<T> сохраняются в отсортиро­ ванном порядке, класс SortedSet<T> оказывается идеальным средством для работы с отсортированными множествами объектов. Коллекция типа SortedSet<T> имеет динамический характер и расширяется по мере необходимости, чтобы вместить все элементы, которые должны в ней храниться.

Ниже перечислены четыре наиболее часто используемые конструктора, определен­ ных в классе SortedSet<T>.

public SortedSet()
public SortedSet(IEnumerable<T> collection)
public SortedSet(IComparer comparer)
public SortedSet(IEnumerable<T> collection, IComparer comparer)

В первой форме конструктора создается пустое множество, а во второй форме — множество, состоящее из элементов указываемой коллекции collection. В третьей форме конструктора допускается указывать способ сравнения с помощью параметра comparer. А в четвертой форме создается множество, состоящее из элементов указы­ ваемой коллекции collection, и используется заданный способ сравнения comparer. Имеется также пятая форма конструктора данного класса, в которой допускается ини­ циализировать множество последовательно упорядоченными данными.

В классе SortedSet<T> реализуется интерфейс ISet<T>, а следовательно, в нем предоставляется полный набор операций со множествами. В этом классе предоставля­ ется также метод GetViewBetween(), возвращающий часть множества в форме объ­ екта типа SortedSet<T>, метод RemoveWhere(), удаляющий из множества элементы, не удовлетворяющие заданному условию, или предикату, а также метод Reverse(), возвращающий объект типа IEnumerable<T>, который циклически проходит множе­ ство в обратном порядке.

Помимо свойств, определенных в интерфейсах, которые реализуются в классе SortedSet<T>, в него введены дополнительные свойства, приведенные ниже.

public IComparer<T> Comparer { get; }
public T Max { get; }
public T Min { get; }

Свойство Comparer получает способ сравнения для вызывающего множества. Свой­ ство Мах получает наибольшее значение во множестве, а свойство Min — наименьшее значение во множестве.

В качестве примера применения класса SortedSet<T> на практике просто замени­ те обозначение HashSet на SortedSet в исходном коде программы из предыдущего подраздела, посвященного коллекциям типа HashSet<T>.

Параллельные коллекции

В версию 4.0 среды .NET Framework добавлено новое пространство имен System. Collections.Concurrent. Оно содержит коллекции, которые являются потокобе­ зопасными и специально предназначены для параллельного программирования. Это означает, что они могут безопасно использоваться в многопоточной программе, где возможен одновременный доступ к коллекции со стороны двух или больше парал­ лельно исполняемых потоков. Ниже перечислены классы параллельных коллекций.

Параллельная коллекция Описание
BlockingCollection<T> Предоставляет оболочку для блокирующей реализации интерфейса IProducerConsumerCollection<T>
ConcurrentBag<T> Обеспечивает неупорядоченную реализацию интерфейса IProducerConsumerCollection<T>, которая оказывается наиболее пригодной в том случае, когда информация вырабатывается и потребляется в одном потоке
ConcurrentDictionary<TKey, TValue> Сохраняет пары "ключ-значение”, а значит, реализует параллельный словарь
ConcurrentQueue<T> Реализует параллельную очередь и соответствующий вариант интерфейса IProducerConsumerCollection<T>
ConcurrentStack<T> Реализует параллельный стек и соответствующий вариант интерфейса IproducerConsumerCollection<T>

Как видите, в нескольких классах параллельных коллекций реализуется интер­ фейс IProducerConsumerCollection. Этот интерфейс также определен в простран­ стве имен System.Collections.Concurrent. Он служит в качестве расширения интерфейсов IEnumerable, IEnumerable<T> и ICollection. Кроме того, в нем определены методы TryAdd() и TryTake(), поддерживающие шаблон "поставщик- потребитель". (Классический шаблон "поставщик-потребитель" отличается решением двух задач. Первая задача производит элементы коллекции, а другая потребляет их.)

Метод TryAdd() пытается добавить элемент в коллекцию, а метод TryTake() — уда­ лить элемент из коллекции. Ниже приведены формы объявления обоих методов.

bool TryAdd(Т item)
bool TryTake(out T item)

Метод TryAdd() возвращает логическое значение true, если в коллекцию добавлен элемент item. А метод TryTake() возвращает логическое значение true, если элемент item удален из коллекции. Если метод TryAdd() выполнен успешно, то элемент item будет содержать объект. (Кроме того, в интерфейсе IProducerConsumerCollection указывается перегружаемый вариант метода СоруТо(), определяемого в интерфейсе ICollection, а также метода ToArray(), копирующего коллекцию в массив.)

Параллельные коллекции зачастую применяются в комбинации с библиотекой распараллеливания задач (TPL) или языком PLINQ. В силу особого характера этих кол­ лекций все их классы не будут рассматриваться далее подробно. Вместо этого на кон­ кретных примерах будет дан краткий обзор класса BlockingCollection<T>. Усвоив основы построения класса BlockingCollection<T>, вы сможете без особого труда разобраться и в остальных классах параллельных коллекций.

В классе BlockingCollection<T>, по существу, реализуется блокирующая оче­ редь. Это означает, что в такой очереди автоматически устанавливается ожидание лю­ бых попыток вставить элемент в коллекцию, когда она заполнена, а также попыток удалить элемент из коллекции, когда она пуста. Это идеальное решение для тех си­ туаций, которые связаны с применением шаблона "поставщик-потребитель". В клас­ се BlockingCollection<T> реализуются интерфейсы ICollection, IEnumerable, IEnumerable<T>, а также IDisposable.

В классе BlockingCollection<T> определяются следующие конструкторы.

public BlockingCollection()
public BlockingCollection(int boundedCapacity)
public BlockingCollection(IProducerConsumerCollection<T> collection)
public BlockingCollection(IProducerConsumerCollection<T> collection,
						int boundedCapacity)

В двух первых конструкторах в оболочку класса BlockingCollection<T> заклю­ чается коллекция, являющаяся экземпляром объекта типа ConcurrentQueue<T>. А в двух других конструкторах можно указать коллекцию, которая должна быть поло­ жена в основу коллекции типа BlockingCollection<T>. Если указывается параметр boundedCapacity, то он должен содержать максимальное количество объектов, кото­ рые коллекция должна содержать перед тем, как она окажется заблокированной. Если же параметр boundedCapacity не указан, то коллекция оказывается неограниченной.

Помимо методов TryAdd() и TryTake(), определяемых параллельно с теми, что указываются в интерфейсе IProducerConsumerCollection<T>, в классе BlockingCollection<T> определяется также ряд собственных методов. Ниже пред­ ставлены методы, которые будут использоваться в приведенных далее примерах.

public void Add(T item)
public T Take()

Когда метод Add() вызывается для неограниченной коллекции, он добавляет элемент item, в коллекцию и затем возвращает управление вызывающей части про­ граммы. А когда метод Add() вызывается для ограниченной коллекции, он блокиру­ ет доступ к ней, если она заполнена. После того как из коллекции будет удален один элемент или больше, указанный элемент item будет добавлен в коллекцию, и затем произойдет возврат из данного метода. Метод Таkе() удаляет элемент из коллекции и возвращает управление вызывающей части программы. (Имеются также варианты обоих методов, принимающие в качестве параметра признак задачи как экземпляр объекта типа CancellationToken.)

Применяя методы Add() и Таке(), можно реализовать простой шаблон "поставщик-потребитель", как показано в приведенном ниже примере программы. В этой программе создается поставщик, формирующий символы от А до Z, а так­ же потребитель, получающий эти символы. При этом создается коллекция типа BlockingCollection<T>, ограниченная 4 элементами.

// Простой пример коллекции типа BlockingCollection.
using System;
using System.Threading.Tasks;
using System.Threading;
using System.Collections.Concurrent;

class BlockingDemo {
	static BlockingCollection<char> be;
	// Произвести и поставить символы от А до Z.
	static void Producer() {
		for(char ch = 'A'; ch <= 'Z'; ch++) {
			be.Add(ch);
			Console.WriteLine("Производится символ " + ch);
		}
	}

	// Потребить 26 символов.
	static void Consumer() {
		for(int i=0; i < 26; i++)
			Console.WriteLine("Потребляется символ " + bc.Take());
	}

	static void Main() {
		// Использовать блокирующую коллекцию, ограниченную 4 элементами.
		be = new BlockingCollection<char>(4);

		// Создать задачи поставщика и потребителя.
		Task Prod = new Task(Producer);
		Task Con = new Task(Consumer);

		// Запустить задачи.
		Con.Start();
		Prod.Start();

		// Ожидать завершения обеих задач.
		try {
			Task.WaitAll(Con, Prod);
		} catch(AggregateException exc) {
			Console.WriteLine(exc);
		} finally {
			Con.Dispose();
			Prod.Dispose();
			bc.Dispose();
		}
	}
}

Если запустить эту программу на выполнение, то на экране появится смешанный результат, выводимый поставщиком и потребителем. Отчасти это объясняется тем, что коллекция bc ограничена 4 элементами, а это означает, что в нее может быть до­ бавлено только четыре элемента, прежде чем ее придется сократить. В качестве экс­ перимента попробуйте сделать коллекцию bc неограниченной и понаблюдайте за по­ лученными результатами. В некоторых средах выполнения это приведет к тому, что все элементы коллекции будут сформированы до того, как начнется какое-либо их по­ требление. Кроме того, попробуйте ограничить коллекцию одним элементом. В этом случае одновременно может быть сформирован лишь один элемент.

Для работы с коллекцией типа BlockingCollection<T> может оказаться полез­ ным и метод CompleteAdding(). Ниже приведена форма его объявления.

public void CompleteAdding()

Вызов этого метода означает, что в коллекцию не будет больше добавлено ни одно­ го элемента. Это приводит к тому, что свойство IsAddingComplete принимает логи­ ческое значение true. Если же коллекция пуста, то свойство IsCompleted принимает логическое значение true, и в этом случае вызовы метода Таке() не блокируются.

Ниже приведены формы объявления свойств IsAddingComplete и IsCompleted.

public bool IsCompleted { get; }
public bool IsAddingComplete { get; }

Когда коллекция типа BlockingCollection<T> только начинает формиро­ ваться, эти свойства содержат логическое значение false. А после вызова метода CompleteAdding() они принимают логическое значение true.

Ниже приведен вариант предыдущего примера программы, измененный с целью продемонстрировать применение метода CompleteAdding(), свойства IsCompleted и метода TryTake().

// Применение методов CompleteAdding(), TryTake() и свойства IsCompleted.
using System;
using System.Threading.Tasks;
using System.Threading;
using System.Collections.Concurrent;

class BlockingDemo {
	static BlockingCollection<char> bc;

	// Произвести и поставить символы от А до Z.
	static void Producer() {
		for(char ch = 'A'; ch <= 'Z'; ch++) {
			bc.Add(ch);
			Console.WriteLine("Производится символ " + ch);
		}
		bc.CompleteAdding();
	}

	// Потреблять символы до тех пор, пока их будет производить поставщик.
	static void Consumer() {
		char ch;
		while(!bc.IsCompleted) {
			if(bc.TryTake(out ch))
				Console.WriteLine("Потребляется символ " + ch);
		}
	}

	static void Main() {
		// Использовать блокирующую коллекцию, ограниченную 4 элементами.
		bc = new BlockingCollection<char>(4);

		// Создать задачи поставщика и потребителя.
		Task Prod = new Task(Producer);
		Task Con = new Task(Consumer);

		// Запустить задачи.
		Con.Start();
		Prod.Start();

		// Ожидать завершения обеих задач.
		try {
			Task.WaitAll(Con, Prod);
		} catch(AggregateException exc) {
			Console.WriteLine(exc);
		} finally {
			Con.Dispose();
			Prod.Dispose();
			bc.Dispose();
		}
	}
}

Этот вариант программы дает такой же результат, как и предыдущий. Главное его отличие заключается в том, что теперь метод Producer() может производить и поставлять сколько угодно элементов. С этой целью он просто вызывает метод CompleteAdding(), когда завершает создание элементов. А метод Consumer() лишь "потребляет" произведенные элементы до тех пор, пока свойство IsCompleted не примет логическое значение true.

Несмотря на специфический до некоторой степени характер параллельных кол­ лекций, предназначенных в основном для параллельного программирования, у них, тем не менее, имеется немало общего с обычными, непараллельными коллекция­ ми, описанными в предыдущих разделах. Если же вам приходится работать в среде параллельного программирования, то для организации одновременного доступа к данным из нескольких потоков вам, скорее всего, придется воспользоваться параллель­ ными коллекциями.

Сохранение объектов, определяемых пользователем классов, в коллекции

Ради простоты приведенных выше примеров в коллекции, как правило, сохраня­ лись объекты встроенных типов, в том числе int, string и char. Но ведь в коллекции можно хранить не только объекты встроенных типов. Достоинство коллекций в том и состоит, что в них допускается хранить объекты любого типа, включая объекты опреде­ ляемых пользователем классов.

Рассмотрим сначала простой пример применения класса необобщенной коллек­ ции ArrayList для хранения информации о товарных запасах. В этом классе инкап­ сулируется класс Inventory.

// Простой пример коллекции товарных запасов.
using System;
using System.Collections;

class Inventory {
	string name;
	double cost;
	int onhand;

	public Inventory(string n, double c, int h) {
		name = n;
		cost = c;
		onhand = h;
	}

	public override string ToString() {
		return
		String.Format("{0,-10}Стоимость: {1,6:С} Наличие: {2}",
				name, cost, onhand);
	}
}

class InventoryList {
	static void Main() {
		ArrayList inv = new ArrayList();

		// Добавить элементы в список.
		inv.Add(new Inventory("Кусачки", 5.95, 3));
		inv.Add(new Inventory("Отвертки", 8.29, 2));
		inv.Add(new Inventory("Молотки", 3.50, 4));
		inv.Add(new Inventory("Дрели", 19.88, 8));

		Console.WriteLine("Перечень товарных запасов:");
		foreach(Inventory i in inv) {
			Console.WriteLine(" " + i);
		}
	}
}

При выполнении программы из данного примера получается следующий результат.

Перечень товарных запасов:
 Кусачки Стоимость: $5.95 Наличие: 3
 Отвертки Стоимость: $8.29 Наличие: 2
 Молотки Стоимость: $3.50 Наличие: 4
 Дрели Стоимость: $19.88 Наличие: 8

Обратите внимание на то, что в данном примере программы не потребовалось ни­ каких специальных действий для сохранения в коллекции объектов типа Inventory. Благодаря тому что все типы наследуют от класса object, в необобщенной коллекции можно хранить объекты любого типа. Именно поэтому в необобщенной коллекции нетрудно сохранить объекты определяемых пользователем классов. Безусловно, это также означает, что такая коллекция не типизирована.

Для того чтобы сохранить объекты определяемых пользователем классов в типи­ зированной коллекции, придется воспользоваться классами обобщенных коллекций. В качестве примера ниже приведен измененный вариант программы из предыдущего примера. В этом варианте используется класс обобщенной коллекции List<T>, а ре­ зультат получается таким же, как и прежде.

// Пример сохранения объектов класса Inventory в
// обобщенной коллекции класса List<T>.
using System;
using System.Collections.Generic;

class Inventory {
	string name;
	double cost;
	int onhand;

	public Inventory(string n, double c, int h) {
		name = n;
		cost = c;
		onhand = h;
	}

	public override string ToString() {
		return
		String.Format("{0,-10}Стоимость: {1,6:С} Наличие: {2}",
			name, cost, onhand);
	}
}

class TypeSafeInventoryList {
	static void Main() {
		List<Inventory> inv = new List<Inventory>();

		// Добавить элементы в список.
		inv.Add(new Inventory("Кусачки", 5.95, 3));
		inv.Add(new Inventory("Отвертки", 8.29, 2));
		inv.Add(new Inventory("Молотки", 3.50, 4));
		inv.Add(new Inventory("Дрели", 19.88, 8));

		Console.WriteLine("Перечень товарных запасов:");
		foreach(Inventory i in inv) {
			Console.WriteLine(" " + i);
		}
	}
}

Данный пример отличается от предыдущего лишь передачей типа Inventory в качестве аргумента типа конструктору класса List<T>. А в остальном оба примера рассматриваемой здесь программы практически одинаковы. Это, по существу, означа­ ет, что для применения обобщенной коллекции не требуется никаких особых усилий, но при сохранении в такой коллекции объекта конкретного типа строго соблюдается типовая безопасность.

Тем не менее для обоих примеров рассматриваемой здесь программы характерна еще одна особенность: они довольно кратки. Если учесть, что для организации дина­ мического массива, где можно хранить, извлекать и обрабатывать данные товарных запасов, потребуется не менее 40 строк кода, то преимущества коллекций сразу же становятся очевидными. Нетрудно догадаться, что рассматриваемая здесь программа получится длиннее в несколько раз, если попытаться закодировать все эти функции коллекции вручную. Коллекции предлагают готовые решения самых разных задач программирования, и поэтому их следует использовать при всяком удобном случае.

У рассматриваемой здесь программы имеется все же один не совсем очевидный недостаток: коллекция не подлежит сортировке. Дело в том, что в классах ArrayList и List отсутствуют средства для сравнения двух объектов типа Inventory. Но из этого положения имеются два выхода. Во-первых, в классе Inventory можно реализо­ вать интерфейс IComparable, в котором определяется метод сравнения объектов дан­ ного класса. И во-вторых, для целей сравнения можно указать объект типа IComparer. Оба подхода рассматриваются далее по очереди.

Реализация интерфейса IComparable

Если требуется отсортировать коллекцию, состоящую из объектов определяе­ мого пользователем класса, при условии, что они не сохраняются в коллекции клас­ са SortedList, где элементы располагаются в отсортированном порядке, то в такой коллекции должен быть известен способ сортировки содержащихся в ней объектов. С этой целью можно, в частности, реализовать интерфейс IComparable для объектов сохраняемого типа. Интерфейс IComparable доступен в двух формах: обобщенной и необобщенной. Несмотря на сходство применения обеих форм данного интерфейса, между ними имеются некоторые, хотя и небольшие, отличия, рассматриваемые ниже.

Реализация интерфейса IComparable для необобщенных коллекций

Если требуется отсортировать объекты, хранящиеся в необобщенной коллек­ ции, то для этой цели придется реализовать необобщенный вариант интерфейса IComparable. В этом варианте данного интерфейса определяется только один метод CompareTo(), который определяет порядок выполнения самого сравнения. Ниже приведена общая форма объявления метода CompareTo().

int CompareTo(object obj)

В методе CompareTo() вызывающий объект сравнивается с объектом obj. Для со­ ртировки объектов по нарастающей конкретная реализация данного метода должна возвращать нулевое значение, если значения сравниваемых объектов равны; положи­ тельное — если значение вызывающего объекта больше, чем у объекта obj; и отри­ цательное — если значение вызывающего объекта меньше, чем у объекта obj. А для сортировки по убывающей можно обратить результат сравнения объектов. Если же тип объекта obj не подходит для сравнения с вызывающим объектом, то в методе CompareTo() может быть сгенерировано исключение ArgumentException.

В приведенном ниже примере программы демонстрируется конкретная реализа­ ция интерфейса IComparable. В этой программе интерфейс IComparable вводится в класс Inventory, разработанный в двух последних примерах из предыдущего раз­ дела. В классе Inventory реализуется метод CompareTo() для сравнения полей name объектов данного класса, что дает возможность отсортировать товарные запасы по наи­ менованию. Как показано в данном примере программы, коллекция объектов класса Inventory подлежит сортировке благодаря реализации интерфейса IComparable в этом классе.

// Реализовать интерфейс IComparable.
using System;
using System.Collections;

// Реализовать необобщенный вариант интерфейса IComparable.
class Inventory : IComparable {
	string name;
	double cost;
	int onhand;

	public Inventory(string n, double c, int h) {
		name = n;
		cost = c;
		onhand = h;
	}

	public override string ToString() {
		return
		String.Format("{0,-10(Стоимость: {1,6:С} Наличие: {2}",
			name, cost, onhand);
	}

	// Реализовать интерфейс IComparable.
	public int CompareTo(object obj) {
		Inventory b;
		b = (Inventory) obj;
		return name.CompareTo(b.name);
	}
}

class IComparableDemo {
	static void Main() {
		ArrayList inv = new ArrayList();

		// Добавить элементы в список.
		inv.Add(new Inventory("Кусачки", 5.95, 3));
		inv.Add(new Inventory("Отвертки", 8.29, 2));
		inv.Add(new Inventory("Молотки", 3.50, 4));
		inv.Add(new Inventory("Дрели", 19.88, 8));

		Console.WriteLine("Перечень товарных запасов до сортировки:");
		foreach(Inventory i in inv) {
			Console.WriteLine(" " + i);
		}
		Console.WriteLine();

		// Отсортировать список.
		inv.Sort();
		Console.WriteLine("Перечень товарных запасов после сортировки:");
		foreach(Inventory i in inv) {
			Console.WriteLine(" " + i);
		}
	}
}

Ниже приведен результат выполнения данной программы. Обратите внимание на то, что после вызова метода Sort() товарные запасы оказываются отсортированными по наименованию.

Перечень товарных запасов до сортировки:
Кусачки	 Стоимость:	 $5.95 Наличие: 3
Отвертки Стоимость:  $8.29 Наличие: 2
Молотки	 Стоимость:  $3.50 Наличие: 4
Дрели	 Стоимость: $19.88 Наличие: 8

Перечень товарных запасов после сортировки:
Дрели	 Стоимость: $19.88 Наличие: 8
Кусачки  Стоимость:  $5.95 Наличие: 3
Молотки  Стоимость:  $3.50 Наличие: 4
Отвертки Стоимость:  $8.29 Наличие: 2

Реализация интерфейса IComparable для обобщенных коллекций

Если требуется отсортировать объекты, хранящиеся в обобщенной коллек­ ции, то для этой цели придется реализовать обобщенный вариант интерфейса IComparable<T>. В этом варианте интерфейса IComparable определяется приведен­ ная ниже обобщенная форма метода CompareTo().

int CompareTo (Т other)

В методе CompareTo() вызывающий объект сравнивается с другим объектом other. Для сортировки объектов по нарастающей конкретная реализация данного метода должна возвращать нулевое значение, если значения сравниваемых объектов равны; положительное — если значение вызывающего объекта больше, чем у объекта другого other; и отрицательное — если значение вызывающего объекта меньше, чем у другого объекта other. А для сортировки по убывающей можно обратить результат сравнения объектов. При реализации обобщенного интерфейса IComparable<T> имя типа реализующего класса обычно передается в качестве аргумента типа.

Приведенный ниже пример программы является вариантом предыдущего при­ мера, измененным с целью реализовать и использовать обобщенный интерфейс IComparable<Т>. Обратите внимание на применение класса обобщенной коллекции List<T> вместо класса необобщенной коллекции ArrayList.

// Реализовать интерфейс IComparable<T>.
using System;
using System.Collections.Generic;

// Реализовать обобщенный вариант интерфейса IComparable<T>.
class Inventory : IComparable<Inventory> {
	string name;
	double cost;
	int onhand;

	public Inventory(string n, double c, int h) {
		name = n;
		cost = c;
		onhand = h;
	}

	public override string ToString() {
		return
		String.Format("{0,-10}Стоимость: {1,6:С} Наличие: {2}",
			name, cost, onhand);
	}

	// Реализовать интерфейс IComparable<T>.
	public int CompareTo(Inventory obj) {
		return name.CompareTo(obj.name);
	}
}

class GenericIComparableDemo {
	static void Main() {
		List<Inventory> inv = new List<Inventory>();

		// Добавить элементы в список.
		inv.Add(new Inventory("Кусачки", 5.95, 3));
		inv.Add(new Inventory("Отвертки", 8.2 9, 2));
		inv.Add(new Inventory("Молотки", 3.50, 4));
		inv.Add(new Inventory("Дрели", 19.88, 8));

		Console.WriteLine("Перечень товарных запасов до сортировки:");
		foreach(Inventory i in inv) {
			Console.WriteLine(" " + i) ;
		}
		Console.WriteLine();

		// Отсортировать список.
		inv.Sort();
		Console.WriteLine("Перечень товарных запасов после сортировки:");
		foreach(Inventory i in inv) {
			Console.WriteLine(" " + i);
		}
	}
}

Эта версия программы дает такой же результат, как и предыдущая, необобщенная версия.

Применение интерфейса IComparer

Для сортировки объектов определяемых пользователем классов зачастую проще всего реализовать в этих классах интерфейс IComparable. Тем не менее данную задачу можно решить и с помощью интерфейса IComparer. Для этой цели необходимо сна­ чала создать класс, реализующий интерфейс IComparer, а затем указать объект этого класса, когда потребуется сравнение.

Интерфейс IComparer существует в двух формах: обобщенной и необобщенной. Несмотря на сходство применения обеих форм данного интерфейса, между ними име­ ются некоторые, хотя и небольшие, отличия, рассматриваемые ниже.

Применение необобщенного интерфейса IComparer

В необобщенном интерфейсе IComparer определяется только один метод, Compare().

int Compare(object x, object y)

В методе Compare() сравниваются объекты x и у. Для сортировки объектов по на­ растающей конкретная реализация данного метода должна возвращать нулевое зна­ чение, если значения сравниваемых объектов равны; положительное — если значение объекта х больше, чем у объекта у; и отрицательное — если значение объекта х мень­ ше, чем у объекта у. А для сортировки по убывающей можно обратить результат срав­ нения объектов. Если же тип объекта х не подходит для сравнения с объектом у, то в методе CompareTo() может быть сгенерировано исключение ArgumentException.

Объект типа IComparer может быть указан при конструировании объекта класса SortedList, при вызове метода ArrayList.Sort(IComparer), а также в ряде других мест в классах коллекций. Главное преимущество применения интерфейса IComparer заключается в том, что сортировке подлежат объекты тех классов, в которых интерфейс IComparable не реализуется.

Приведенный ниже пример программы является вариантом рассматривавшегося ранее необобщенного примера программы учета товарных запасов, переделанного с целью воспользоваться интерфейсом IComparer для сортировки перечня товарных за­ пасов. В этом варианте программы сначала создается класс CompInv, в котором реали­ зуется интерфейс IComparer и сравниваются два объекта класса Inventory. А затем объект класса CompInv указывается в вызове метода Sort() для сортировки перечня товарных запасов.

// Использовать необобщенный вариант интерфейса IComparer.
using System;
using System.Collections;

// Создать объект типа IComparer для объектов класса Inventory.
class CompInv : IComparer {
	// Реализовать интерфейс IComparer.
	public int Compare(object x, object y) {
		Inventory, a, b;
		a = (Inventory) x;
		b = (Inventory) y;
		return string.Compare(a.name, b.name, StringComparison.Ordinal);
	}
}

class Inventory {
	public string name;
	double cost;
	int onhand;

	public Inventory(string n, double c, int h) {
		name = n;
		cost = c;
		onhand = h;
	}

	public override string ToString() {
		return
		String.Format("{0,-10} Цена: {1,6:С} В наличии: {2}",
			name, cost, onhand);
	}
}

class IComparerDemo {
	static void Main() {
		CompInv comp = new CompInv();
		ArrayList inv = new ArrayList();

		// Добавить элементы в список.
		inv.Add(new Inventory("Кусачки", 5.95, 3));
		inv.Add(new Inventory("Отвертки", 8.29, 2));
		inv.Add(new Inventory("Молотки", 3.50, 4));
		inv.Add(new Inventory ("Дрели", 19.88, 8));

		Console.WriteLine("Перечень товарных запасов до сортировки:");
		foreach(Inventory i in inv) {
			Console.WriteLine(" " + i);
		}
		Console.WriteLine();

		// Отсортировать список, используя интерфейс IComparer.
		inv.Sort(comp);
		Console.WriteLine("Перечень товарных запасов после сортировки:");
		foreach(Inventory i in inv) {
			Console.WriteLine(" " + i);
		}
	}
}

Эта версия программы дает такой же результат, как и предыдущая.

Применение обобщенного интерфейса IComparer

Интерфейс IComparer<T> является обобщенным вариантом интерфейса IComparer. В нем определяется приведенный ниже обобщенный вариант метода Compare().

int Compare(Т х, Т у)

В этом методе сравниваются объекты х и у и возвращается нулевое значение, если значения сравниваемых объектов равны; положительное — если значение объекта х больше, чем у объекта у; и отрицательное — если значение объекта х меньше, чем у объекта у.

Ниже приведена обобщенная версия предыдущей программы учета товарных за­ пасов, в которой теперь используется интерфейс IComparer<T>. Она дает такой же результат, как и необобщенная версия этой же программы.

// Использовать обобщенный вариант интерфейса IComparer<T>.
using System;
using System.Collections.Generic;

// Создать объект типа IComparer<T> для объектов класса Inventory.
class CompInv<T> : IComparer<T> where T : Inventory {
	// Реализовать интерфейс IComparer<T>.
	public int Compare(T x, T y) {
		return string.Compare(x.name, y.name, StringComparison.Ordinal);
	}
}

class Inventory {
	public string name;
	double cost;
	int onhand;

	public Inventory(string n, double c, int h) {
		name = n;
		cost = c;
		onhand = h;
	}

	public override string ToString() {
		return
		String.Format("{0,-10} Цена: {1,6:С} В наличии: {2}",
			name, cost, onhand);
	}
}

class GenericIComparerDemo {
	static void Main() {
		CompInv<Inventory> comp = new CompInv<Inventory>();
		List<Inventory> inv = new List<Inventory>();

		// Добавить элементы в список.
		inv.Add(new Inventory("Кусачки", 5.95, 3));
		inv.Add(new Inventory("Отвертки", 8.29, 2));
		inv.Add(new Inventory("Молотки", 3.50, 4));
		inv.Add(new Inventory("Дрели", 19.88, 8));

		Console.WriteLine("Перечень товарных запасов до сортировки:");
		foreach(Inventory i in inv) {
			Console.WriteLine(" " + i);
		}
		Console.WriteLine ();

		// Отсортировать список, используя интерфейс IComparer.
		inv.Sort(comp);
		Console.WriteLine("Перечень товарных запасов после сортировки:");
		foreach(Inventory i in inv) {
			Console.WriteLine(" " + i);
		}
	}
}

Применение класса StringComparer

В простых примерах из этой главы указывать явно способ сравнения символьных строк совсем не обязательно. Но это может потребоваться в тех случаях, когда строки сохраняются в отсортированной коллекции или когда строки ищутся либо сортиру­ ются в коллекции. Так, если строки должны быть отсортированы с учетом настроек одной культурной среды, а затем их приходится искать с учетом настроек другой куль­ турной среды, то во избежание ошибок, вероятнее всего, потребуется указать способ сравнения символьных строк. Аналогичная ситуация возникает и при хешировании коллекции. Для подобных (и других) случаев в конструкторах классов некоторых кол­ лекций предусмотрена поддержка параметра типа IComparer. С целью явно указать способ сравнения символьных строк этому параметру передается в качестве аргумента экземпляр объекта класса StringComparer.

Класс StringComparer был подробно описан в главе 21 при рассмотрении вопросов сортировки и поиска в массивах. В этом классе реализуются интерфейсы IComparer, IComparer<String>, IEqualityComparer, а также IEqualityComparer<String>. Следовательно, экземпляр объекта типа StringComparer может быть передан па­ раметру типа IComparer в качестве аргумента. В классе StringComparer опреде­ ляется несколько доступных только для чтения свойств, возвращающих экземпляр объекта типа StringComparer, который поддерживает различные способы срав­ нения символьных строк. Как пояснялось в главе 21, к числу этих свойств относятся следующие: CurrentCulture, CurrentCultureIgnoreCase, InvariantCulture, InvariantCultureIgnoreCase, Ordinal, а также OrdinalIgnoreCase. Все эти свой­ ства можно использовать для явного указания способа сравнения символьньгх строк.

В качестве примера ниже показано, как коллекция типа SortedList<TKey, TValue> конструируется для хранения символьных строк, ключи которых сравнива­ ются порядковым способом.

SortedList<string, int> users =
	new SortedList<string, int>(StringComparer.Ordinal);

Доступ к коллекции с помощью перечислителя

К элементам коллекции нередко приходится обращаться циклически, например, для отображения каждого элемента коллекции. С этой целью можно, с одной сторо­ ны, организовать цикл foreach, как было показано в приведенных выше примерах, а с другой — воспользоваться перечислителем. Перечислитель — это объект, который реализует необобщенный интерфейс IEnumerator или обобщенный интерфейс IEnumerator<Т>.

В интерфейсе IEnumerator определяется одно свойство, Current, необобщенная форма которого приведена ниже.

object Current { get; }

А в интерфейсе IEnumerator<T> объявляется следующая обобщенная форма свойства Current.

Т Current { get; }

В обеих формах свойства Current получается текущий перечисляемый элемент коллекции. Но поскольку свойство Current доступно только для чтения, то перечис­ литель может служить только для извлечения, но не видоизменения объектов в кол­ лекции.

В интерфейсе IEnumerator определяются два метода. Первым из них является ме­ тод MoveNext(), объявляемый следующим образом.

bool MoveNext()

При каждом вызове метода MoveNext() текущее положение перечислителя сме­ щается к следующему элементу коллекции. Этот метод возвращает логическое значе­ ние true, если следующий элемент коллекции доступен, и логическое значение false, если достигнут конец коллекции. Перед первым вызовом метода MoveNext() значение свойства Current оказывается неопределенным. (В принципе до первого вызова ме­ тода MoveNext() перечислитель обращается к несуществующему элементу, который должен находиться перед первым элементом коллекции. Именно поэтому приходится вызывать метод MoveNext(), чтобы перейти к первому элементу коллекции.)

Для установки перечислителя в исходное положение, соответствующее началу кол­ лекции, вызывается приведенный ниже метод Reset().

void Reset()

После вызова метода Reset() перечисление вновь начинается с самого начала кол­ лекции. Поэтому, прежде чем получить первый элемент коллекции, следует вызвать метод MoveNext().

В интерфейсе IEnumerator<T> методы MoveNext() и Reset() действуют по тому же самому принципу.

Необходимо также обратить внимание на два следующих момента. Во-первых, перечислитель нельзя использовать для изменения содержимого перечисляемой с его помощью коллекции. Следовательно, перечислители действуют по отношению к коллекции как к доступной только для чтения. И во-вторых, любое изменение в пере­ числяемой коллекции делает перечислитель недействительным.

Применение обычного перечислителя

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

  1. Получить перечислитель, устанавливаемый в начало коллекции, вызвав для этой коллекции метод GetEnumerator().
  2. Организовать цикл, в котором вызывается метод MoveNext(). Повторять цикл до тех пор, пока метод MoveNext() возвращает логическое значение true.
  3. Получить в цикле каждый элемент коллекции с помощью свойства Current.

Ниже приведен пример программы, в которой реализуется данная процедура. В этой программе используется класс ArrayList, но общие принципы циклического обращения к элементам коллекции с помощью перечислителя остаются неизменны­ ми для коллекций любого типа, в том числе и обобщенных.

// Продемонстрировать применение перечислителя.
using System;
using System.Collections;

class EnumeratorDemo {
	static void Main() {
		ArrayList list = new ArrayList(1);
		for (int i=0; i < 10; i++)
		list.Add(i);

		// Использовать перечислитель для доступа к списку.
		IEnumerator etr = list.GetEnumerator();
		while(etr.MoveNext())
			Console.Write(etr.Current + " ");
		Console.WriteLine();

		// Повторить перечисление списка.
		etr.Reset();
		while(etr.MoveNext())
			Console.Write(etr.Current + " ");
		Console.WriteLine();
	}
}

Вот к какому результату приводит выполнение этой программы.

0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9

Вообще говоря, для циклического обращения к элементам коллекции цикл foreach оказывается более удобным, чем перечислитель. Тем не менее перечисли­ тель предоставляет больше возможностей для управления, поскольку его можно при желании всегда установить в исходное положение.

Применение перечислителя типа IDictionaryEnumerator

Если для организации коллекции в виде словаря, например типа Hashtable, реализуется необобщенный интерфейс IDictionary, то для циклического обра­ щения к элементам такой коллекции следует использовать перечислитель типа IDictionaryEnumerator вместо перечислителя типа IEnumerator. Интерфейс IDictionaryEnumerator наследует от интерфейса IEnumerator и имеет три допол­ нительных свойства. Первым из них является следующее свойство.

DictionaryEntry Entry { get; }

Свойство Entry позволяет получить пару "ключ-значение" из перечислителя в форме структуры DictionaryEntry. Напомним, что в структуре DictionaryEntry определяются два свойства, Key и Value, с помощью которых можно получать доступ к ключу или значению, связанному с элементом коллекции. Ниже приведены два дру­ гих свойства, определяемых в интерфейсе IDictionaryEnumerator.

object Key { get; }
object Value { get; }

С помощью этих свойств осуществляется непосредственный доступ к ключу или значению.

Перечислитель типа IDictionaryEnumerator используется аналогично обычному перечислителю, за исключением того, что текущее значение в данном случае получа­ ется с помощью свойств Entry, Key или Value, а не свойства Current. Следовательно, приобретя перечислитель типа IDictionaryEnumerator, необходимо вызвать метод MoveNext(), чтобы получить первый элемент коллекции. А для получения остальных ее элементов следует продолжить вызовы метода MoveNext(). Этот метод возвращает логическое значение false, когда в коллекции больше нет ни одного элемента. В приведенном ниже примере программы элементы коллекции типа Hashtable перечисляются с помощью перечислителя типа IDictionaryEnumerator.

// Продемонстрировать применение перечислителя типа IDictionaryEnumerator.
using System;
using System.Collections;

class IDicEnumDemo {
	static void Main() {
		// Создать хеш-таблицу.
		Hashtable ht = new Hashtable();

		// Добавить элементы в таблицу.
		ht.Add("Кен", "555-7756");
		ht.Add("Мэри", "555-9876");
		ht.Add("Том", "555-3456");
		ht.Add("Тодд", "555-3452");

		// Продемонстрировать применение перечислителя.
		IDictionaryEnumerator etr = ht.GetEnumerator();
		Console.WriteLine("Отобразить информацию с помощью свойства Entry.");
		while(etr.MoveNext())
			Console.WriteLine(etr.Entry.Key + ": " + etr.Entry.Value);

		Console.WriteLine();

		Console.WriteLine("Отобразить информацию " +
						"с помощью свойств Key и Value.");
		etr.Reset();
		while(etr.MoveNext())
			Console.WriteLine(etr.Key + ": " + etr.Value);
	}
}

Ниже приведен результат выполнения этой программы.

Отобразить информацию с помощью свойства Entry.
Мэри: 555-9876
Том: 555-3456
Тодд: 555-3452
Кен: 555-7756

Отобразить информацию с помощью свойств Key и Value.
Мэри: 555-9876
Том: 555-3456
Тодд: 555-3452
Кен: 555-7756

Реализация интерфейсов IEnumerable и IEnumerator

Как упоминалось выше, для циклического обращения к элементам коллекции за­ частую проще (да и лучше) организовать цикл foreach, чем пользоваться непосред­ ственно методами интерфейса IEnumerator. Тем не менее ясное представление о принципе действия подобных интерфейсов важно иметь по еще одной причине: если требуется создать класс, содержащий объекты, перечисляемые в цикле foreach, то в этом классе следует реализовать интерфейсы IEnumerator и IEnumerable. Иными словами, для того чтобы обратиться к объекту определяемого пользователем класса в цикле foreach, необходимо реализовать интерфейсы IEnumerator и IEnumerable в их обобщенной или необобщенной форме. Правда, сделать это будет нетрудно, по­ скольку оба интерфейса не очень велики.

В приведенном ниже примере программы интерфейсы IEnumerator и IEnumerable реализуются в необобщенной форме, с тем чтобы перечислить содер­ жимое массива, инкапсулированного в классе MyClass.

// Реализовать интерфейсы IEnumerable и IEnumerator.
using System;
using System.Collections;

class MyClass : IEnumerator, IEnumerable {
	char[] chrs = { 'А', 'В', 'C', 'D' };
	int idx = -1;

	// Реализовать интерфейс IEnumerable.
	public IEnumerator GetEnumerator() {
		return this;
	}

	// В следующих методах реализуется интерфейс IEnumerator
	// Возвратить текущий объект.
	public object Current {
		get {
			return chrs[idx];
		}
	}

	// Перейти к следующему объекту.
	public bool MoveNext() {
		if(idx == chrs.Length-1) {
			Reset(); // установить перечислитель в конец
			return false;
			}
		idx++;
		return true;
	}

	// Установить перечислитель в начало.
	public void Reset() { idx = -1; }
}

class EnumeratorImplDemo {
	static void Main() {
		MyClass me = new MyClass();

		// Отобразить содержимое объекта me.
		foreach(char ch in me)
			Console.Write(ch + " ");
		Console.WriteLine();

		// Вновь отобразить содержимое объекта me.
		foreach(char ch in me)
			Console.Write(ch + " ");
		Console.WriteLine();
	}
}

Эта программа дает следующий результат.

А В С D
А В С D

В данной программе сначала создается класс MyClass, в котором инкапсулируется небольшой массив типа char, состоящий из символов А-D. Индекс этого массива хра­ нится в переменной idx, инициализируемой значением -1. Затем в классе MyClass ре­ ализуются оба интерфейса, IEnumerator и IEnumerable. Метод GetEnumerator() возвращает ссылку на перечислитель, которым в данном случае оказывается текущий объект. Свойство Current возвращает следующий символ в массиве, т.е. объект, ука­ зываемый по индексу idx. Метод MoveNext() перемещает индекс idx в следующее положение. Этот метод возвращает логическое значение false, если достигнут конец коллекции, в противном случае — логическое значение true. Напомним, что перечис­ литель оказывается неопределенным вплоть до первого вызова метода MoveNext(). Следовательно, метод MoveNext() автоматически вызывается в цикле foreach перед обращением к свойству Current. Именно поэтому первоначальное значение пере­ менной idx устанавливается равным -1. Оно становится равным нулю на первом шаге цикла foreach. Обобщенная реализация рассматриваемых здесь интерфейсов будет действовать по тому же самому принципу.

Далее в методе Main() создается объект mc типа MyClass, и содержимое этого объекта дважды отображается в цикле foreach.

Применение итераторов

Как следует из предыдущих примеров, реализовать интерфейсы IEnumerator и IEnumerable нетрудно. Но еще проще воспользоваться итератором, который пред­ ставляет собой метод, оператор или аксессор, возвращающий по очереди члены со­ вокупности объектов от ее начала и до конца. Так, если некоторый массив состоит из пяти элементов, то итератор данного массива возвратит все эти элементы по очереди. Реализовав итератор, можно обращаться к объектам определяемого пользователем класса в цикле foreach.

Обратимся сначала к простому примеру итератора. Приведенная ниже программа является измененной версией предыдущей программы, в которой вместо явной реали­ зации интерфейсов IEnumerator и IEnumerable применяется итератор.

// Простой пример применения итератора.
using System;
using System.Collections;

class MyClass {
	char[] chrs = { 'A', 'B', 'C', 'D' };

	// Этот итератор возвращает символы из массива chrs.
	public IEnumerator GetEnumerator() {
		foreach(char ch in chrs)
			yield return ch;
	}
}

class ItrDemo {
	static void Main() {
		MyClass me = new MyClass();
		foreach(char ch in me)
			Console.Write(ch + " ");
		Console.WriteLine();
	}
}

При выполнении этой программы получается следующий результат.

А В С D

Как видите, содержимое массива mc.chrs перечислено.

Рассмотрим эту программу более подробно. Во-первых, обратите внимание на то, что в классе MyClass не указывается IEnumerator в качестве реализуемого интерфей­ са. При создании итератора компилятор реализует этот интерфейс автоматически. И во-вторых, обратите особое внимание на метод GetEnumerator(), который ради удобства приводится ниже еще раз.

// Этот итератор возвращает символы из массива chrs.
public IEnumerator GetEnumerator() {
	foreach(char ch in chrs)
		yield return ch;
}

Это и есть итератор для объектов класса MyClass. Как видите, в нем явно реализу­ ется метод GetEnumerator(), определенный в интерфейсе IEnumerable. А теперь перейдем непосредственно к телу данного метода. Оно состоит из цикла foreach, в котором возвращаются элементы из массива chrs. И делается это с помощью опе­ ратора yield return. Этот оператор возвращает следующий объект в коллекции, которым в данном случае оказывается очередной символ в массиве chrs. Благодаря этому средству обращение к объекту mc типа MyClass организуется в цикле foreach внутри метода Main().

Обозначение yield служит в языке C# в качестве контекстного ключевого слова. Это означает, что оно имеет специальное назначение только в блоке итератора. А вне этого блока оно может быть использовано аналогично любому другому идентификатору. Следует особо подчеркнуть, что итератор не обязательно должен опираться на мас­ сив или коллекцию другого типа. Он должен просто возвращать следующий элемент из совокупности элементов. Это означает, что элементы могут быть построены дина­ мически с помощью соответствующего алгоритма. В качестве примера ниже приведе­ на версия предыдущей программы, в которой возвращаются все буквы английского алфавита, набранные в верхнем регистре. Вместо массива буквы формируются в цикле for.

// Пример динамического построения значений,
// возвращаемых по очереди с помощью итератора.
using System;
using System.Collections;

class MyClass {
	char ch = 'A';
	// Этот итератор возвращает буквы английского
	// алфавита, набранные в верхнем регистре.
	public IEnumerator GetEnumerator() {
		for(int i=0; i < 26; i++)
			yield return (char) (ch + i);
	}
}

class ItrDemo2 {
	static void Main() {
		MyClass me = new MyClass();
		foreach(char ch in me)
			Console.Write(ch + " ");
		Console.WriteLine();
	}
}

Вот к какому результату приводит выполнение этой программы.

A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

Прерывание итератора

Для преждевременного прерывания итератора служит следующая форма опера­ тора yield.

yield break;

Когда этот оператор выполняется, итератор уведомляет о том, что достигнут конец коллекции. А это, по существу, останавливает сам итератор.

Приведенная ниже программа является версией предыдущей программы, изме­ ненной с целью отобразить только первые десять букв английского алфавита.

// Пример прерывания итератора.
using System;
using System.Collections;

class MyClass {
	char ch = 'A';
	// Этот итератор возвращает первые 10 букв английского алфавита.
	public IEnumerator GetEnumerator() {
		for(int i=0; i < 26; i++) {
			if(i == 10) yield break; // прервать итератор преждевременно
			yield return (char) (ch + i);
		}
	}
}

class ItrDemo3 {
	static void Main() {
		MyClass mc = new MyClass();
		foreach(char ch in mc)
			Console.Write(ch + " ");
		Console.WriteLine();
	}
}

Эта программа дает следующий результат.

A B C D E F G H I J

Применение нескольких операторов yield

В итераторе допускается применение нескольких операторов yield. Но каждый такой оператор должен возвращать следующий элемент в коллекции. В качестве при­ мера рассмотрим следующую программу.

// Пример применения нескольких операторов yield.
using System;
using System.Collections;

class MyClass {
	// Этот итератор возвращает буквы А, В, С, D и Е.
	public IEnumerator GetEnumerator() {
		yield return 'A';
		yield return 'B';
		yield return 'C';
		yield return 'D';
		yield return 'E';
	}
}

class ItrDemo5 {
	static void Main() {
		MyClass me = new MyClass ();
		foreach(char ch in mc)
			Console.Write(ch + " ");
		Console.WriteLine();
	}
}

Ниже приведен результата выполнения этой программы.

А В С D Е

В данной программе внутри метода GetEnumerator() выполняются пять опера­ торов yield. Следует особо подчеркнуть, что они выполняются по очереди и каждый раз, когда из коллекции получается очередной элемент. Таким образом, на каждом шаге цикла foreach в методе Main() возвращается только один символ.

Создание именованного итератора

В приведенных выше примерах был продемонстрирован простейший способ ре­ ализации итератора. Но ему имеется альтернатива в виде именованного итератора. В данном случае создается метод, оператор или аксессор, возвращающий ссылку на объект типа IEnumerable. Именно этот объект используется в коде для предоставле­ ния итератора. Именованный итератор представляет собой метод, общая форма кото­ рого приведена ниже:

public IEnumerable имя_итератора(список_параметров) {
	// ...
	yield return obj;
}

где имя_итератора обозначает конкретное имя метода; список_параметров — от нуля до нескольких параметров, передаваемых методу итератора; obj — следующий объект, возвращаемый итератором. Как только именованный итератор будет создан, его можно использовать везде, где он требуется, например для управления циклом foreach.

Именованные итераторы оказываются весьма полезными в некоторых ситуациях, поскольку они позволяют передавать аргументы итератору, управляющему процес­ сом получения конкретных элементов из коллекции. Например, итератору можно передать начальный и конечный пределы совокупности элементов, возвращаемых из коллекции итератором. Эту форму итератора можно перегрузить, расширив ее функ­ циональные возможности. В приведенном ниже примере программы демонстриру­ ются два способа применения именованного итератора для получения элементов кол­ лекции. В одном случае элементы перечисляются в заданных начальном и конечном пределах, а в другом — элементы перечисляются с начала последовательности и до указанного конечного предела.

// Использовать именованные итераторы.
using System;
using System.Collections;

class MyClass {
	char ch = 'A';

	// Этот итератор возвращает буквы английского алфавита,
	// начиная с буквы А и кончая указанным конечным пределом.
	public IEnumerable MyItr(int end) {
		for(int i=0; i < end; i++)
			yield return (char) (ch + i);
	}

	// Этот итератор возвращает буквы в заданных пределах.
	public IEnumerable MyItr(int begin, int end) {
		for(int i=begin; i < end; i++)
			yield return (char) (ch + i);
	}
}

class ItrDemo4 {
	static void Main() {
		MyClass mc = new MyClass();

		Console.WriteLine("Возвратить по очереди первые 7 букв:");
		foreach(char ch in mc.MyItr(7))
			Console.Write(ch + " ");
		Console.WriteLine("\n");

		Console.WriteLine("Возвратить по очереди буквы от F до L:");
		foreach(char ch in mc.MyItr(5, 12))
			Console.Write(ch + " ");
		Console.WriteLine();
	}
}

Эта программа дает следующий результат.

Возвратить по очереди первые 7 букв:
А В С D Е F G

Возвратить по очереди буквы от F до L:
F G Н I J К L

Создание обобщенного итератора

В приведенных выше примерах применялись необобщенные итераторы, но, конеч­ но, ничто не мешает создать обобщенные итераторы. Для этого достаточно возвратить объект обобщенного типа IEnumerator<T> или IEnumerable<T>. Ниже приведен пример создания обобщенного итератора.

// Простой пример обобщенного итератора.
using System;
using System.Collections.Generic;

class MyClass<T> {
	T[] array;

	public MyClass(T[] a) {
		array = a;
	}

	// Этот итератор возвращает символы из массива chrs.
	public IEnumerator<T> GetEnumerator() {
		foreach(T obj in array)
			yield return obj;
	}
}

class GenericItrDemo {
	static void Main() {
		int[] nums = { 4, 3, 6, 4, 7, 9 };

		MyClass<int> me = new MyClass<int>(nums);
		foreach(int x in mc)
			Console.Write(x + " ");
		Console.WriteLine();

		bool[] bVals = { true, true, false, true };

		MyClass<bool> mc2 = new MyClass<bool>(bVals);
		foreach(bool b in mc2)
			Console.Write(b + " ");
		Console.WriteLine();
	}
}

Вот к какому результату приводит выполнение этой программы.

4 3 6 4 7 9
True True False True

В данном примере массив, состоящий из возвращаемых по очереди объектов, пере­ дается конструктору класса MyClass. Тип этого массива указывает в качестве аргумен­ та типа в конструкторе класса MyClass.

Метод GetEnumerator() оперирует данными обобщенного типа т и возвраща­ ет перечислитель типа IEnumerator<T>. Следовательно, итератор, определенный в классе MyClass, способен перечислять данные любого типа.

Инициализаторы коллекций

В C# имеется специальное средство, называемое инициализатором коллекции и упро­ щающее инициализацию некоторых коллекций. Вместо того чтобы явно вызывать ме­ тод Add(), при создании коллекции можно указать список инициализаторов. После этого компилятор организует автоматические вызовы метода Add(), используя значе­ ния из этого списка. Синтаксис в данном случае ничем не отличается от инициализа­ ции массива. Обратимся к следующему примеру, в котором создается коллекция типа List<char>, инициализируемая символами С, А, Е, В, D и F.

List<char> lst = new List<char>() { 'С', 'А', 'Е', 'В', 'D', 'F' };

После выполнения этого оператора значение свойства lst.Count будет равно 6, поскольку именно таково число инициализаторов. А после выполнения следующего цикла foreach:

foreach(ch in lst)
	Console.Write(ch + " ");

получится такой результат:

С A E В D F

Для инициализации коллекции типа LinkedList<TKey, TValue>, в которой хра­ нятся пары "ключ-значение", инициализаторы приходится предоставлять парами, как показано ниже.

SortedList<int, string> lst =
	new SortedList<int, string>() { {1, "один"}, {2, "два" }, {3, "три"} };

Компилятор передаст каждую группу значений в качестве аргументов методу Add(). Следовательно, первая пара инициализаторов преобразуется компилятором в вызов Add(1, "один").

Компилятор вызывает метод Add() автоматически для ввода инициализаторов в коллекцию, и поэтому инициализаторы коллекций можно использовать только в кол­ лекциях, поддерживающих открытую реализацию метода Add(). Это означает, что инициализаторы коллекций нельзя использовать в коллекциях типа Stack, Stack<T>, Queue или Queue<T>, поскольку в них метод Add() не поддерживается. Их нельзя применять также в тех коллекциях типа LinkedList<T>, где метод Add() предостав­ ляется как результат явной реализации соответствующего интерфейса.