Читаем Полное руководство. С# 4.0 полностью

Получить в цикле каждый элемент коллекции с помощью свойства 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 или IEnumerable. Ниже приведен пример создания обобщенного итератора. // Простой пример обобщенного итератора. using System; using System.Collections.Generic; class MyClass { T[] array; public MyClass(T[] a) { array = a; } // Этот итератор возвращает символы из массива chrs. public IEnumerator GetEnumerator { foreach(T obj in array) yield return obj; } } class GenericItrDemo { static void Main { int[] nums = { 4, 3, 6, 4, 7, 9 }; MyClass me = new MyClass(nums); foreach(int x in mc) Console.Write(x + " "); Console.WriteLine; bool[] bVals = { true, true, false, true }; MyClass mc2 = new MyClass(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. Следовательно, итератор, определенный в классе MyClass, способен перечислять данные любого типа. Инициализаторы коллекций В C# имеется специальное средство, называемое инициализатором коллекции и упро щающее инициализацию некоторых коллекций. Вместо того чтобы явно вызывать ме тод Add, при создании коллекции можно указать список инициализаторов. После этого компилятор организует автоматические вызовы метода Add, используя значе ния из этого списка. Синтаксис в данном случае ничем не отличается от инициализа ции массива. Обратимся к следующему примеру, в котором создается коллекция типа List, инициализируемая символами С, А, Е, В, D и F. List lst = new List { 'С', 'А', 'Е', 'В', 'D', 'F' }; После выполнения этого оператора значение свойства lst.Count будет равно 6, поскольку именно таково число инициализаторов. А после выполнения следующего цикла foreach: foreach(ch in lst) Console.Write(ch + " "); получится такой результат: С A E В D F Для инициализации коллекции типа LinkedList, в которой хра нятся пары "ключ-значение", инициализаторы приходится предоставлять парами, как показано ниже. SortedList lst = new SortedList { {1, "один"}, {2, "два" }, {3, "три"} }; Компилятор передаст каждую группу значений в качестве аргументов методу Add. Следовательно, первая пара инициализаторов преобразуется компилятором в вызов Add(1, "один"). Компилятор вызывает метод Add автоматически для ввода инициализаторов в коллекцию, и поэтому инициализаторы коллекций можно использовать только в кол лекциях, поддерживающих открытую реализацию метода Add. Это означает, что инициализаторы коллекций нельзя использовать в коллекциях типа Stack, Stack, Queue или Queue, поскольку в них метод Add не поддерживается. Их нельзя применять также в тех коллекциях типа LinkedList, где метод Add предостав ляется как результат явной реализации соответствующего интерфейса.

Перейти на страницу:

Похожие книги

Основы программирования в Linux
Основы программирования в Linux

В четвертом издании популярного руководства даны основы программирования в операционной системе Linux. Рассмотрены: использование библиотек C/C++ и стан­дартных средств разработки, организация системных вызовов, файловый ввод/вывод, взаимодействие процессов, программирование средствами командной оболочки, создание графических пользовательских интерфейсов с помощью инструментальных средств GTK+ или Qt, применение сокетов и др. Описана компиляция программ, их компоновка c библиотеками и работа с терминальным вводом/выводом. Даны приемы написания приложений в средах GNOME® и KDE®, хранения данных с использованием СУБД MySQL® и отладки программ. Книга хорошо структурирована, что делает обучение легким и быстрым. Для начинающих Linux-программистов

Нейл Мэтью , Ричард Стоунс , Татьяна Коротяева

ОС и Сети / Программирование / Книги по IT
97 этюдов для архитекторов программных систем
97 этюдов для архитекторов программных систем

Успешная карьера архитектора программного обеспечения требует хорошего владения как технической, так и деловой сторонами вопросов, связанных с проектированием архитектуры. В этой необычной книге ведущие архитекторы ПО со всего света обсуждают важные принципы разработки, выходящие далеко за пределы чисто технических вопросов.?Архитектор ПО выполняет роль посредника между командой разработчиков и бизнес-руководством компании, поэтому чтобы добиться успеха в этой профессии, необходимо не только овладеть различными технологиями, но и обеспечить работу над проектом в соответствии с бизнес-целями. В книге более 50 архитекторов рассказывают о том, что считают самым важным в своей работе, дают советы, как организовать общение с другими участниками проекта, как снизить сложность архитектуры, как оказывать поддержку разработчикам. Они щедро делятся множеством полезных идей и приемов, которые вынесли из своего многолетнего опыта. Авторы надеются, что книга станет источником вдохновения и руководством к действию для многих профессиональных программистов.

Билл де Ора , Майкл Хайгард , Нил Форд

Программирование, программы, базы данных / Базы данных / Программирование / Книги по IT
Программист-прагматик. Путь от подмастерья к мастеру
Программист-прагматик. Путь от подмастерья к мастеру

Находясь на переднем крае программирования, книга "Программист-прагматик. Путь от подмастерья к мастеру" абстрагируется от всевозрастающей специализации и технических тонкостей разработки программ на современном уровне, чтобы исследовать суть процесса – требования к работоспособной и поддерживаемой программе, приводящей пользователей в восторг. Книга охватывает различные темы – от личной ответственности и карьерного роста до архитектурных методик, придающих программам гибкость и простоту в адаптации и повторном использовании.Прочитав эту книгу, вы научитесь:Бороться с недостатками программного обеспечения;Избегать ловушек, связанных с дублированием знания;Создавать гибкие, динамичные и адаптируемые программы;Избегать программирования в расчете на совпадение;Защищать вашу программу при помощи контрактов, утверждений и исключений;Собирать реальные требования;Осуществлять безжалостное и эффективное тестирование;Приводить в восторг ваших пользователей;Формировать команды из программистов-прагматиков и с помощью автоматизации делать ваши разработки более точными.

А. Алексашин , Дэвид Томас , Эндрю Хант

Программирование / Книги по IT