Справочник по C# Герберт Шилдт ббк 32. 973. 26018 75 Ш57 удк 681 07 Издательский дом "Вильямс" Зав редакцией
Скачать 5.05 Mb.
|
625 Важно отметить, что Hashtable -коллекция не гарантирует сохранения порядка элементов. Дело в том, что хеширование обычно не применяется к отсортированным таблицам. Рассмотрим пример, который демонстрирует использование Hashtable -коллекции: // Демонстрация использования Hashtable-коллекции. using System; using System.Collections; class HashtableDemo { public static void Main() { // Создаем хеш-таблицу. Hashtable ht = new Hashtable(); // Добавляем элементы в хеш-таблицу. ht.Add("здание", "жилое помещение"); ht.Add("автомобиль", "транспортное средство"); ht.Add("книга", "набор печатных слов"); ht.Add("яблоко", "съедобный фрукт"); // Добавлять элементы можно также С помощью индексатора. ht["трактор"] = "сельскохозяйственная машина"; // Получаем коллекцию ключей. ICollection c = ht.Keys; // Используем ключи для получения значений. foreach(string str in c) Console.WriteLine(str + ": " + ht[str]); } } Результаты выполнения этой программы таковы: яблоко: съедобный фрукт здание: жилое помещение трактор: сельскохозяйственная машина автомобиль: транспортное средство книга: набор печатных слов Как видно по приведенным результатам, пары ключ/значение хранятся отнюдь не в упорядоченном виде. Обратите внимание на то, как было получено и отображено содержимое хеш-таблицы ht . Во-первых, коллекция ключей считывается с помощью свойства Keys . Каждый ключ затем используется в качестве индекса хеш-таблицы ht , который позволяет найти и отобразить значение, соответствующее каждому ключу. Не забывайте, что индексатор, определенный в интерфейсе IDictionary и реализованный в классе Hashtable , использует ключ в роли индекса. Класс SortedList Класс SortedList предназначен для создания коллекции, которая хранит пары ключ/значение в упорядоченном виде, а именно отсортированы по ключу. Класс SortedList реализует интерфейсы IDictionary , ICollection , IEnumerable и ICloneable В классе SortedList определено несколько конструкторов, включая следующие: 626 Часть II. Библиотека C# public SortedList() public SortedList(IDictionary c ) public SortedList(int capacity ) public SortedList(IComparer comp ) Первый конструктор позволяет создать пустую коллекцию с начальной емкостью, равной 16 элементам. Второй создает SortedList -коллекцию, которая инициализируется элементами и емкостью коллекции, заданной параметром с . Третий конструктор предназначен для построения пустого SortedList -списка, который инициализируется емкостью, заданной параметром capacity . Как вы помните, емкость— это размер базового массива, который используется для хранения элементов коллекции. Четвертая форма конструктора позволяет задать метод сравнения, который должен использоваться для сравнения объектов списка. С помощью этой формы создается пустая коллекция с начальной емкостью, равной 16 элементам. Емкость SortedList -коллекции увеличивается автоматически, если в этом возникает необходимость, при добавлении элементов. Если окажется, что текущая емкость может быть превышена, она удваивается. Преимущество задания емкости при создании SortedList -списка состоит в минимизации затрат системных ресурсов, связанных с изменением размера коллекции. Конечно, задавать начальную емкость имеет смысл только в том случае, если вы знаете, какое количество элементов должно храниться в коллекции. В классе SortedList помимо методов, определенных в реализованных им интерфейсах, также определены собственные методы. Наиболее употребимые приведены в табл. 22.6. Чтобы определить, содержит ли SortedList -коллекция заданный ключ, достаточно вызвать метод ContainsKey() . А чтобы узнать, хранится ли в списке заданное значение, вызовите метод ContainsValue() . Для опроса элементов SortedList -коллекции необходимо получить нумератор типа IDictionaryEnumerator , вызвав метод GetEnumerator() . Вспомните, что для опроса содержимого коллекции, в которой хранятся пары ключ/значение, используется именно класс IDictionaryEnumerator . Синхронизированную версию SortedList - коллекции можно получить с помощью метода Synchronized() Таблица 22.6. Наиболее употребимые методы, определенные в классе SortedList Метод Описание public virtual bool ContainsKey( object k ) Возвращает значение true , если в вызывающей SortedList - коллекции содержится ключ, заданный параметром k. В противном случае возвращает значение false public virtual bool ContainsValue( object v ) Возвращает значение true , если в вызывающей SortedList - коллекции содержится значение, заданное параметром v. В противном случае возвращает значение false public virtual object GetByIndex( int idx ) Возвращает значение, индекс которого задан параметром idx public virtual IDictionaryEnumerator GetEnumerator() Возвращает нумератор типа IDictionaryEnumerator для вызывающей SortedList -коллекции public virtual object GetKey( int idx ) Возвращает ключ, индекс которого задан параметром idx public virtual IList GetKeyList() Возвращает IList -коллекцию ключей, хранимых в вызывающей SortedList -коллекции public virtual IList GetValueList() Возвращает IList -коллекцию значений, хранимых в вызывающей SortedList -коллекции Глава 22. Работа с коллекциями 627 Окончание табл. 22.6 Метод Описание public virtual int IndexOfKey( object k ) Возвращает индекс ключа, заданного параметром k Возвращает значение -1, если в списке нет заданного ключа public virtual int IndexOfValue( object v ) Возвращает индекс первого вхождения значения, заданного параметром v . Возвращает -1, если в списке нет заданного ключа public virtual void GetByIndex( int idx , object v ) Устанавливает значение по индексу, заданному параметром idx , равным значению, переданному в параметре v public static SortedList Synchronized( SortedList sl ) Возвращает синхронизированную версию SortedList - коллекции, переданной в параметре sl public virtual void TrimToSize() Устанавливает свойство Capacity равным значению свойства Count Существуют различные способы установки и считывания ключа либо значения. Чтобы получить значение, связанное с заданным индексом, вызовите метод GetByIndex() , а чтобы установить значение, заданное индексом, — метод SetByIndex() . Получить ключ, связанный с заданным индексом, можно с помощью метода GetKey() . Для получения списка всех ключей используйте метод GetKeyList() , а для получения списка всех значений— метод GetValueList() . Получить индекс ключа можно с помощью метода IndexOfKey() , а индекс значения— с помощью метода IndexOfValue() . Класс SortedList также поддерживает индексатор, определенный интерфейсом IDictionary , благодаря чему можно устанавливать или считывать значение, заданное соответствующим ключом. В классе SortedList помимо свойств, определенных в реализованных им интерфейсах, определены два собственных свойства. Получить предназначенную только для чтения коллекцию ключей или значений, хранимых в SortedList -коллекции, можно с помощью таких свойств: public virtual ICollection Keys { get; } public virtual ICollection Values { get; } Порядок следования ключей и значений в полученных коллекциях отражает порядок SortedList -коллекции. Подобно Hashtable -коллекции, SortedList -список хранит пары ключ/значение в форме структуры типа DiсtionaryEntry , но с помощью методов и свойств, определенных в классе SortedList , программисты обычно получают отдельный доступ к ключам и значениям. Использование отсортированного списка типа SortedList демонстрируется в программе, которая представляет собой переработанную и расширенную версию программы из предыдущего раздела, демонстрировавшей Hashtable -коллекцию. Изучая результаты выполнения этой программы, обратите внимание на то, что SortedList - коллекция отсортирована по ключу. // Демонстрация SortedList-коллекции. using System; using System.Collections; class SLDemo { public static void Main() { // Создаем упорядоченную коллекцию типа SortedList. SortedList sl = new SortedList(); 628 Часть II. Библиотека C# // Добавляем в список элементы. sl.Add("здание", "жилое помещение"); sl.Add("автомобиль", "транспортное средство"); sl.Add("книга", "набор печатных слов"); sl.Add("яблоко", "съедобный фрукт"); // Добавлять элементы можно также с помощью индексатора. sl["трактор"] = "сельскохозяйственная машина"; // Получаем коллекцию ключей. ICollection c = sl.Keys; // Используем ключи для получения значений. Console.WriteLine( "Содержимое списка, полученное с помощью " + "индексатора."); foreach(string str in c) Console.WriteLine(str + ": " + sl[str]); Console.WriteLine(); // Отображаем список, используя целочисленные индексы. Console.WriteLine( "Содержимое списка, полученное с помощью " + "целочисленных индексов."); for(int i=0; i Console.WriteLine(); // Отображаем целочисленные индексы элементов списка. Console.WriteLine( "Целочисленные индексы элементов списка."); foreach(string str in c) Console.WriteLine(str + ": " + sl.IndexOfKey(str)); } } Результаты выполнения этой программы таковы: Содержимое списка, полученное с помощью индексатора. автомобиль: транспортное средство здание: жилое помещение книга: набор печатных слов трактор: сельскохозяйственная машина яблоко: съедобный фрукт Содержимое списка, полученное с помощью целочисленных индексов. транспортное средство жилое помещение набор печатных слов сельскохозяйственная машина съедобный фрукт Целочисленные индексы элементов списка. автомобиль: 0 здание: 1 книга: 2 трактор: 3 яблоко: 4 Глава 22. Работа с коллекциями 629 Класс Stack Вероятно, большинству читателей известно, что стек представляет собой список, добавление и удаление элементов к которому осуществляется по принципу “последним пришел — первым обслужен” (last-in, first-out — LIFO). Чтобы понять, как работает стек, представьте себе груду тарелок на столе. Тарелку, поставленную на стол первой, можно будет взять лишь последней, т.е. когда будут сняты все поставленные сверху тарелки. Стек — наиболее востребованная структура данных в программировании. Ее часто используют в системном программном обеспечении, компиляторах и программах из области создания искусственного интеллекта (в частности, в сфере программирования с обратным слежением). Класс коллекции, предназначенный для поддержки стека, называется Stack . Он реализует интерфейсы ICollection , IEnumerable и ICloneable . Стек— это динамическая коллекция, которая при необходимости увеличивается, чтобы принять для хранения новые элементы, причем каждый раз, когда стек должен расшириться, его емкость удваивается. В классе Stack определены следующие конструкторы: public Stack() public Stack(int capacity ) public Stack(ICollection c ) Первый конструктор предназначен для создания пустого стека с начальной емкостью, равной 10 элементам. Второй создает пустой стек с начальной емкостью, заданной параметром capacity . Третий конструктор служит для построения стека, который инициализируется элементами и емкостью коллекции, заданной параметром с Помимо методов, определенных в интерфейсах, которые реализует класс Stack , в нем определены также собственные методы, перечисленные в табл. 22.7. По описанию этих методов можно судить о том, как используется стек, Чтобы поместить объект в вершину стека, вызовите метод Push() . Чтобы извлечь верхний элемент и удалить его из стека, используйте метод Pop() . Если при вызове метода Pop() окажется, что вызывающий стек пуст, генерируется исключение типа InvalidOperationException . Метод Peek() позволяет вернуть верхний элемент, не удаляя его из стека. Таблица 22.7. Методы, определенные в классе Stack Метод Описание public virtual bool Contains( object v ) Возвращает значение true , если объект v содержится в вызывающем стеке. В противном случае возвращает значение false public virtual void Clear() Устанавливает свойство Count равным нулю, тем самым эффективно очищая стек public virtual object Peek() Возвращает элемент, расположенный в вершине стека, но не удаляет его public virtual object Pop() Возвращает элемент, расположенный в вершине стека, и удаляет его public virtual void Push( object v ) Помещает объект v в стек public static stack Synchronized( stack stk ) Возвращает синхронизированную версию Stack -списка, переданного в параметре stk public virtual object[] ToArray() Возвращает массив, который содержит копии элементов вызывающего стека Рассмотрим пример создания стека и его использования: поместим в него несколько объектов класса Integer , а затем извлечем их. 630 Часть II. Библиотека C# // Демонстрация использования класса 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(); } public 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 ), возникающая при попытке извлечь элемент из пустого стека (эта ситуация называется незагруженностью стека). Глава 22. Работа с коллекциями 631 Помещаем элемент в стек: Push(22) Содержимое стека: 22 Помещаем элемент в стек: Push(65) Содержимое стека: 65 22 Помещаем элемент в стек: Push(91) Содержимое стека: 91 65 22 Извлекаем элемент из стека: Pop -> 91 Содержимое стека: 65 22 Извлекаем элемент из стека: Pop -> 65 Содержимое стека: 22 Извлекаем элемент из стека: Pop -> 22 Содержимое стека: Извлекаем элемент из стека: Pop -> Стек пуст. Класс Queue Еще одной распространенной структурой данных является очередь. Добавление элементов в очередь и удаление их из нее осуществляется по принципу “первым пришел — первым обслужен” (first-in, first-out — FIFO). Другими словами, первый элемент, помещенный в очередь, первым же из нее и извлекается. Ну кто не сталкивался с очередями в реальной жизни? Например, каждому приходилось, вероятно, стоять в очереди к билетной кассе в кинотеатре или к кассе в супермаркете, чтобы оплатить покупку. В программировании очереди используются для организации таких механизмов, как выполнение нескольких процессов в системе и поддержка списка незаконченных транзакций (в системах ведения баз данных) или пакетов данных, полученных из Internet. Очереди также часто используются в области имитационного моделирования. Класс коллекции, предназначенный для поддержки очереди, называется Queue . Он реализует интерфейсы ICollection , IEnumerable и ICloneable . Очередь — это динамическая коллекция, которая при необходимости увеличивается, чтобы принять для хранения новые элементы, причем каждый раз, когда такая необходимость возникает, текущий размер очереди умножается на коэффициент роста, который по умолчанию равен значению 2,0. В классе Queue определены следующие конструкторы: public Queue() public Queue(int capacity ) public Queue(int capacity , float growFact ) public Queue(ICollection c ) Первый конструктор предназначен для создания пустой очереди с начальной емкостью, равной 32 элементам, и коэффициентом роста 2,0. Второй создает пустую очередь с начальной емкостью, заданной параметром capacity , и коэффициентом роста 2,0. Третий отличается от второго тем, что позволяет задать коэффициент роста посредством параметра growFact . Четвертый конструктор служит для создания очереди, которая инициализируется элементами и емкостью коллекции, заданной параметром с Помимо методов, определенных в интерфейсах, которые реализует класс Queue , в нем определены также собственные методы, перечисленные в табл. 22.8. О работе очереди можно получить представление по описанию этих методов. Чтобы поместить объект в очередь, вызовите метод Enqueue() . Чтобы извлечь верхний элемент и удалить его из очереди, используйте метод Dequeue() . Если при вызове метода Dequeue() окажется, что вызывающая очередь пуста, генерируется исключение типа InvalidOperationException . Метод Реек() позволяет вернуть следующий объект, не удаляя его из очереди. 632 Часть II. Библиотека C# Таблица 22.8. Методы, определенные в классе Queue Метод Описание public virtual bool Contains( object v ) Возвращает значение true , если объект v содержится в вызывающей очереди. В противном случае возвращает значение false public virtual void Clear() Устанавливает свойство Count равным нулю, тем самым эффективно очищая очередь public virtual object Dequeue() Возвращает объект из начала вызывающей очереди, Возвращаемый объект из очереди удаляется public virtual void Enqueue( object v ) Добавляет объект v в конец очереди public virtual object Peek() Возвращает объект из начала вызывающей очереди, но не удаляет его public static Queue Synchronized( Queue q ) Возвращает синхронизированную версию очереди, заданной параметром q 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(); } public static void Main() { Queue q = new Queue(); foreach(int i in q) Глава 22. Работа с коллекциями 633 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 и ICloneable В классе BitArray определено множество конструкторов. Например, BitArray - коллекцию можно создать из массива булевых значений, используя этот конструктор: public BitArray(bool[] bits ) В этом случае каждый элемент массива bits становится битом BitArray - коллекции. При этом каждый бит в коллекции соответствует элементу массива bits . Более того, порядок элементов массива bits аналогичен порядку битов в коллекции. BitArray -коллекцию можно также создать из массива байтов. Для этого используйте следующий конструктор: public BitArray(byte[] bits ) Здесь битами коллекции становится набор битов, содержащийся в массиве bits , причем элемент bits [0] определяет первые восемь битов, элемент bits [l] — вторые 634 Часть II. Библиотека C# восемь битов и т.д. Подобным образом можно создать BitArray -коллекцию из массива int -значений, используя следующий конструктор: public BitArray(int[] bits ) В данном случае элемент bits [0] определяет первые 32 бита, элемент bits [l] — вторые 32 бита и т.д. С помощью этого конструктора можно создать BitArray -коллекцию заданного размера: public BitArray(int size ) Здесь параметр size задает количество битов в коллекции, причем все они инициализируются значением false Чтобы задать размер создаваемой коллекции и начальное значение ее битов, используйте следующий конструктор: public BitArray(int size , bool v ) Здесь все биты в коллекции будут установлены равными значению, переданному в параметре v Наконец, новую BitArray -коллекцию можно создать из уже существующей. Для этого достаточно обратиться к конструктору public BitArray(BitArray bits ) Новый объект будет содержать такую же коллекцию битов, как у представленной параметром bits , но эти две коллекции изолированы. BitArray -коллекции могут быть индексированными. Каждый индекс соответствует определенному биту, причем нулевой индекс соответствует младшему биту. В классе BitArray помимо методов, определенных в реализованных им интерфейсах, определены также собственные методы (см. табл. 22.9). Обратите внимание на то, что класс BitArray не поддерживает метод Synchronized() . Это означает, что для данного типа коллекции синхронизированную оболочку получить нельзя, и поэтому свойство IsSynchronized всегда равно значению false . Тем не менее доступом к BitArray -коллекции можно управлять посредством синхронизации объекта, возвращаемого свойством SyncRoot Таблица 22.9. Методы, определенные в классе BitArray Метод Описание public BitArray And( BitArray ba ) Выполняет операцию логического умножения (И) между соответствующими битами вызывающего объекта и битами коллекции, заданной параметром ba . Возвращает BitArray - коллекцию, содержащую результат public bool Get( int idx ) Возвращает значение бита, индекс которого задан параметром idx public BitArray Not() Выполняет операцию поразрядного логического отрицания (НЕ) на битах вызывающей коллекции и возвращает BitArray -коллекцию, содержащую результат public BitArray Or( BitArray ba ) Выполняет операцию логического сложения (ИЛИ) между соответствующими битами вызывающего объекта и битами коллекции, заданной параметром ba . Возвращает BitArray - коллекцию, содержащую результат Public void Set( int idx , bool v ) Устанавливает бит, индекс которого задан параметром idx , равным значению параметра v public void SetAll( bool v ) Устанавливает все биты равными значению v public BitArray Xor( BitArray ba ) Выполняет операцию исключающего ИЛИ между соответствующими битами вызывающего объекта и битами коллекции, заданной параметром ba . Возвращает BitArray - коллекцию, содержащую результат Глава 22. Работа с коллекциями 635 В классе BitArray помимо свойств, определенных в реализованных им интерфейсах, определено также собственное свойство Length : public int Length { get; set; } Свойство Length устанавливает или возвращает текущее количество битов в коллекции. Следовательно, свойство Length содержит то же значение, что и стандартное свойство Count , которое определено для всех коллекций. Однако свойство Count предназначено только для чтения, чего не скажешь о свойстве Length . Таким образом, свойство Length можно использовать для изменения размера BitArray -коллекции. Если вы ее уменьшаете, “лишние” биты усекаются со стороны старших разрядов. Если же BitArray -коллекцию увеличивать, то со стороны старших разрядов добавляются биты, имеющие значение false В классе BitArray определен следующий индексатор: object this[int idx ] { 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"); } public static void Main() { BitArray ba = new BitArray(8); byte[] b = { 67 }; BitArray ba2 = new BitArray(b); showbits("Исходное содержимое битовой коллекции ba:", ba); ba = ba.Not(); showbits( "Содержимое коллекции ba после вызова метода Not():", ba); showbits("Содержимое коллекции ba2:", ba2); BitArray ba3 = ba.Xor(ba2); showbits("Результат операции ba XOR ba2:", ba3); } } Результаты выполнения этой программы таковы: Исходное содержимое битовой коллекции ba : False False False False False False False False 636 Часть II. Библиотека C# Содержимое коллекции ba после вызова метода Not(): True True True True True True True True Содержимое коллекции ba2: True True False False False False True False Результат операции ba XOR ba2: False False True True True True False True Специализированные коллекции В среде .NET Framework предусмотрена возможность создания специализированных коллекций, которые оптимизированы для работы с конкретными типами данных или для особого вида обработки Эти классы коллекций (они определены в пространстве имен System.Collections.Specialized ) перечислены в следующей таблице Специализированная коллекция Описание Coiiectionsutii Коллекция, в которой игнорируются различия между строчным и прописным написанием символов в строках HybridDictionary Коллекция, в которой для хранения небольшого числа пар ключ/значение используется класс ListDictionary Но при превышении коллекцией определенного размера для хранения элементов автоматически используется класс Hashtable ListDictionary Коллекция, в которой для хранения пар ключ/значение используется связный список Такую коллекцию рекомендуется использовать лишь при небольшом количестве элементов NameValueCollection Отсортированная коллекция пар ключ/значение, в которой как ключ, так и значение имеют тип string StringCollection Коллекция, оптимизированная для хранения строк StringDictionary Хеш-таблица предназначенная для хранения пар ключ/значение, в которой как ключ, так и значение имеют тип string В пространстве имен System.Collections также определены три абстрактных базовых класса, CollectionBase , ReadOnlyCollectionBase и DictionaryBase , которые предполагают создание производных классов и предназначены для использования в качестве отправной точки при разработке программистом собственных специализированных классов Доступ к коллекциям с помощью нумератора Часто при работе с коллекциями возникает необходимость в циклическом опросе ее элементов Например, нужно отобразить все элементы коллекции Один из способов достижения этого — использование цикла foreach , что и было продемонстрировано в предыдущем примере Еще один способ — использование нумератора Нумератор представляет собой объект, который реализует интерфейс IEnumerator В интерфейсе IEnumerator определено единственное свойство Current object Current { get; } Глава 22. Работа с коллекциями 637 Свойство Current позволяет получить элемент, соответствующий текущему значению нумератора. Поскольку свойство Current предназначено только для чтения, нумератор можно использовать только для считывания значения объекта в коллекции, а не для его модификации. В интерфейсе IEnumerator определены два метода. Первый, MoveNext() , объявляется так: bool MoveNext() При каждом обращении к методу MoveNext() текущая позиция нумератора перемешается к следующему элементу коллекции. Метод возвращает значение true , если к следующему элементу можно получить доступ, или значение false , если достигнут конец коллекции. До выполнения первого обращения к методу MoveNext() значение свойства Current неопределено. Установить нумератор в начало коллекции можно с помощью метода Reset() : void Reset() После вызова метода Reset() нумерация элементов начнется с начала коллекции, и для доступа к первому ее элементу необходимо вызвать метод MoveNext() Использование нумератора Чтобы получить доступ к коллекции с помощью нумератора, нужно сначала его реализовать. В каждом классе коллекции предусмотрен метод GetEnumerator() , который возвращает нумератор, устанавливая его в начало коллекции. Используя этот метод, можно поэлементно опросить всю коллекцию. Чтобы использовать нумератор для циклического обхода коллекции, выполните такие действия: 1. Получите нумератор (с установкой его в начало коллекции), вызвав метод GetEnumerator() 2. Организуйте цикл, в котором вызывается метод MoveNext() . Позаботьтесь о том, чтобы цикл работал до тех пор, пока метод MoveNext() возвращает значение true 3. На каждой итерации цикла опросите соответствующий элемент коллекции с помощью свойства Current В представленной ниже программе используется класс ArrayList , но продемонстрированные здесь принципы работы с динамическим массивом применимы к любому типу коллекции. // Демонстрация использования нумератора. using System; using System.Collections; class EnumeratorDemo { public 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 + " "); 638 Часть II. Библиотека C# Console.WriteLine(); // Устанавливаем позицию нумератора в начало коллекции. etr.Reset(); while(etr.MoveNext()) Console.Write(etr.Current + " "); Console.WriteLine(); } } Результаты выполнения программы выглядят так: 0123456789 0123456789 В общем случае, если нужно в цикле опросить содержимое коллекции, цикл foreach — более удобное средство, чем нумератор. Однако нумератор при желании позволяет “одним махом” вернуться в начало коллекции. Использование интерфейса IDictionaryEnumerator Класс коллекции, который реализует интерфейс 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() . Когда все элементы коллекции будут исчерпаны, метод MoveNext() возвратит значение false Теперь самое время рассмотреть пример использования нумератора типа IDictionaryEnumerator для опроса элементов коллекции типа Hashtable : // Демонстрация использования нумератора // типа IDictionaryEnumerator. using System; using System.Collections; class IDicEnumDemo { public static void Main() { // Создаем хеш-таблицу. Глава 22. Работа с коллекциями 639 Hashtable ht = new Hashtable(); // Добавляем элементы в хеш-таблицу. ht.Add("Анатолий", "555-3456"); ht.Add("Марина", "555-9876"); ht.Add("Александр", "555-3452"); ht.Add("Кирилл", "555-7756"); // Демонстрируем работу нумератора. 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-3456 Марина: 555-9876 Александр: 555-3452 Кирилл: 555-7756 Отображаем информацию с помощью свойств Key и Value. Анатолий: 555-3456 Марина: 555-9876 Александр: 555-3452 Кирилл: 555-7756 Хранение в коллекциях классов, определенных пользователем Для простоты в предыдущих примерах демонстрировалось хранение в коллекциях значений таких встроенных типов, как int , string или char . Безусловно, на коллекции такое ограничение не налагается. А совсем даже наоборот. Мощь коллекций как раз и состоит в том, что они могут хранить объекты любого типа, включая объекты классов, создаваемых программистами. Рассмотрим, например, программу, в которой для хранения информации о запасах товаров на складе, инкапсулированной классом Inventory , используется класс ArrayList // Простой пример на тему инвентаризации. using System; 640 Часть II. Библиотека C# 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,-16}Цена: {1,8:С} В наличии: {2}", name, cost, onhand); } } class InventoryList { public 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 , в любой коллекции общего назначения можно сохранить объект любого типа. Вероятно, вы заметили, что рассматриваемая программа довольно невелика. Если к тому же учесть, что в ней создается динамический массив, который способен хранить, считывать и обрабатывать информацию о содержимом склада, и на все это потребовалось меньше 40 строк кода, то могущество коллекций начинает не просто проявляться, а становится очевидным. Вероятно, многим читателям понятно, что если этот механизм ведения складского хозяйства закодировать вручную, то программа была бы в несколько раз больше. Поэтому подчеркнем еще раз, что коллекции предлагают готовые решения для широкого круга задач. Глава 22. Работа с коллекциями 641 Все же справедливости ради отметим, что предыдущая программа имеет одно ограничение, которое, возможно, сразу и не бросается в глаза: эта коллекция не поддается сортировке. Дело в том, что наша ArrayList -коллекция не “знает”, как сравнивать два объекта типа Inventory . Есть два способа исправить ситуацию. Во-первых, в классе Inventory можно реализовать интерфейс IComparable . Именно этот интерфейс определяет, как сравниваются два объекта класса. Во-вторых, когда потребуется выполнить операцию сравнения, можно определить объект типа IComparer . Оба упомянутых варианта иллюстрируются в следующих разделах. Реализация интерфейса IComparable Если необходимо отсортировать динамический массив (типа ArrayList ) объектов, определенных пользователем (или если вам понадобится сохранить эти объекты в коллекции типа SortedList ), то вы должны сообщить коллекции информацию о том, как сравнивать эти объекты. Один из способов — реализовать интерфейс IComparable . В этом интерфейсе определен только один метод CompareTo() , который позволяет определить, как должно выполняться сравнение объектов соответствующего типа. Общий формат использования метода CompareTo() таков: int CompareTo(object obj ) Метод CompareTo() сравнивает вызывающий объект с объектом, заданным параметром obj . Чтобы отсортировать объекты коллекции в возрастающем порядке, этот метод (в вашей реализации) должен возвращать нуль, если сравниваемые объекты равны, положительное значение, если вызывающий объект больше объекта obj , и отрицательное число, если вызывающий объект меньше объекта obj . Для сортировки в убывающем порядке достаточно инвертировать результат описанного сравнения. Метод CompareTo() может сгенерировать исключение типа ArgumentException , если тип объекта obj несовместим с вызывающим объектом. Рассмотрим пример реализации интерфейса IComparable . Здесь в класс inventory, разработанный в предыдущем разделе, добавляется интерфейс IComparable , что позволяет эффективно отсортировать коллекцию объектов типа Inventory // Реализация интерфейса IComparable. using System; using System.Collections; 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,-16}Цена: {1,8:C} В наличии: {2}", name, cost, onhand); } 642 Часть II. Библиотека C# // Реализуем интерфейс IComparable. public int CompareTo(object obj) { Inventory b; b = (Inventory) obj; return name.CompareTo(b.name); } } class IComparableDemo { public 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 Информация о запасах на складе после сортировки: Гаечные ключи Цена: $8.29 В наличии: 2 Молотки Цена: $3.50 В наличии: 4 Плоскогубцы Цена: $5.95 В наличии: 3 Сверла Цена: $19.88 В наличии: 8 Использование интерфейса IComparer Несмотря на то что реализация интерфейса IComparable для создаваемых программистом классов — самый простой способ решить проблему сортировки объектов (этих классов) в коллекции, возможен и другой подход к ее решению. Он состоит в использовании интерфейса IComparer . В этом случае необходимо сначала создать класс, который реализует интерфейс IComparer , а затем задать объект этого класса Глава 22. Работа с коллекциями 643 именно тогда, когда требуется выполнить сравнение. В интерфейсе IComparer определен только один метод Compare() : int Compare(object obj1 , object obj2 ) Метод Compare() сравнивает объект obj1 с объектом obj2 . Для сортировки в возрастающем порядке метод Compare() должен возвратить нуль, если сравниваемые объекты равны, положительное значение, если объект obj1 больше объекта obj2 , и отрицательное число, если объект obj1 меньше объекта obj2 . Для сортировки в убывающем порядке достаточно инвертировать результат описанного сравнения. Метод CompareTo() может сгенерировать исключение типа ArgumentException , если типы сравниваемых объектов несовместимы. Интерфейс IComparer можно определить при создании объекта коллекции типа SortedList , а именно при вызове метода ArrayList.Sort(IComparer) , а также в других случаях определения классов коллекций. Основное достоинство использования интерфейса IComparer состоит в том, что он позволяет сортировать объекты классов, которые не реализуют интерфейс IComparable Следующая программа представляет собой еще одну вариацию на тему инвентаризации склада. На этот раз для сортировки списка товаров используется интерфейс IComparer . Сначала здесь создается класс CompInv , который реализует интерфейс IComparer и обеспечивает возможность сравнения двух объектов типа Inventory Затем каждый объект этого класса используется в вызове метода Sort() , который, собственно, и сортирует список товаров. // Использование интерфейса IComparer. using System; using System.Collections; // Создаем класс CompInv. // для объектов класса Inventory. class CompInv : IComparer { // Реализуем интерфейс IComparer. public int Compare(object obj1, object obj2) { Inventory a, b; a = (Inventory) obj1; b = (Inventory) obj2; return a.name.CompareTo(b.name); } } 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,-16}Цена: {1,8:С} В наличии: {2}", name, cost, onhand); 644 Часть II. Библиотека C# } } class MailList { public 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); } } } Результаты выполнения этой программы совпадают с предыдущими. Резюме Коллекции вооружают программиста мощным набором удачных решений, подходящих для множества распространенных задач программирования. Когда потребуется обеспечить эффективное хранение и считывание информации, непременно вспомните о коллекциях. Не следует оставлять коллекции лишь для таких “глобальных задач”, как создание корпоративных баз данных, списков рассылки или систем управления складом. Они не менее эффективны в применении к более мелким проблемам. Например, коллекция типа SortedList прекрасно подошла бы для ведения списка ежедневных деловых встреч. Сферу применения коллекций могут ограничить лишь возможности вашего воображения. |