1629295403-b876e2087bddebea4bc9666fb2377a02 (846199), страница 91
Текст из файла (страница 91)
Программа преобразует возвращаемый объект в C o n t a i n e d D a t a O b j e c t передтем, как присвоить его переменной c o n t a i n e d . Вызов C u r r e n t некорректен, еслипредшествующий вызов метода M o v e N e x t ( ) н е вернул t r u e .Глава 20. Работа с коллекциями463Использование foreachМетоды I E n u m e r a t o r достаточно стандартны для того, чтобы С# использовали ихавтоматически для реализации конструкции f o r e a c h .Циклf o r e a c h может обращаться к любому классу, реализующему интерфейсI E n u m e r a b l e , как показано в приведенной обобщенной функции, которая может работать с любым классом — от массивов и связанных списков до стеков и очередей:voidMyFunction(IEnumerablecontainerOfStrings){foreach(stringsincontainerOfStrings){Console.WriteLine("Следующаястрока-{о}",s);}}Класс реализует I E n u m e r a b l e путем определения метода G e t E n u m e r a t o r ( ) ,который возвращает экземпляр I E n u m e r a t o r .
Скрыто от посторонних глазf o r e a c h вызывает метод G e t E n u m e r a t o r () для получения итератора. Цикл использует этот итератор для обхода контейнера. Каждый выбираемый им элементприводится к соответствующему типу перед тем, как продолжить выполнение телацикла. Обратите внимание, что I E n u m e r a b l e и I E n u m e r a t o r различные, но связанные интерфейсы. С# 2.0 предоставляет обобщенную версию обоих интерфейс о в — см.
информацию о пространстве имен S y s t e m . C o l l e c t i o n s . G e n e r i cв справочной системе.Итак, цикл f o r e a c h можно записать таким образом:foreach(intnValueinmyContainer){// ...}Это эквивалентно следующему циклу f o r :for(IEnumerator i =myContainer.GetEnumerator();i.MoveNext();){int//nValue=(int)i.Current//////ИнициализацияУсловиеПустой инкремент////Получениеэлементатекущего}Раздел инициализации цикла f o r получает итератор. Раздел условия используетM o v e N e x t () для определения конца контейнера. M o v e N e x t () сам увеличивает указатель, т.е.
раздел инкремента цикла пуст. (Тем не менее, вам все равно следует указатьточку с запятой после раздела условия, после которой не следует никакой код. В приведенном фрагменте исходного текста это точка с запятой после i . M o v e N e x t ( ) . ) В первой строке цикла выбирается очередной объект и преобразуется к i n t ( C u r r e n t всегдавозвращает тип O b j e c t ) .
Если возвращенный объект не является i n t , С# генерируетисключение неверного преобразования типа.464Часть VII. Дополнительные главыОбращение к элементам массива очень простое и понятное: команда c o n t a i n e r [п]обеспечивает обращение к и-му элементу массива c o n t a i n e r . Было бы хорошо, еслибы так же просто можно было обращаться и к другим типам коллекций.С# позволяет написать вам свою собственную реализацию операции индексирования. Вы можете обеспечить возможностью обращения через индекс коллекции, которые таким свойством изначально не обладают. Кроме того, вы можете индексироватьс использованием в качестве индексов не только типа i n t , но и других типов, например, s t r i n g .Формат индексатораИндексатор выглядит очень похоже на свойство, за тем исключением, что в нем вместо имени свойства появляются ключевое слово t h i s и оператор индекса [ ] :classMyArray{public{getstring this[intindex]// Обратите внимание на// ключевое с л о в о " t h i s "{returnarray[index];}set{array[index]=value;}За сценой выражение s = m y A r r a y [ i ] ; вызывает функцию доступа g e t , передавая е й значение индекса i .
Выражение m y A r r a y [ i ] = " с т р о к а " ; приводит к вызовуфункции доступа s e t , которой передаются индекс i и строка " с т р о к а " .Пример программы с использованием индексатораИндексы не ограничены типом i n t . Например, вы можете использовать для индексирования коллекции домов имена их владельцев или адреса. Кроме того, свойство индексатора может быть перегружено для различных типов индекса.Приведенная далее демонстрационная программа I n d e x e r генерируеткласс виртуального массива K e y e d A r r a y , который выглядит и функционирует точно так же, как и обычный массив, с тем исключением, что в качествеего индексов применяется значение типа s t r i n g .// I n d e x e r - д а н н а я д е м о н с т р а ц и о н н а я программа иллюстрирует// и с п о л ь з о в а н и е о п е р а т о р а и н д е к с а для о б е с п е ч е н и я доступа к// м а с с и в у с и с п о л ь з о в а н и е м с т р о к в к а ч е с т в е и н д е к с о вГлава 20.
Работа с коллекциями465usingSystem;namespaceIndexer{publicclassKeyedArray// Следующая с т р о к а о б е с п е ч и в а е т"ключ" к массиву/ / с т р о к а ,которая идентифицирует элементprivatestring[]sKeys;////object представляетс ключомprivateobject []KeyedArrayразмераII//public-собойреальныеданные,—этосвязанныеoArrayElements;созданиеKeyedArray(intKeyedArrayфиксированногоnSize)sKeys = new s t r i n g [ n S i z e ] ;oArrayElements= new o b j e c t [ n S i z e ] ;// Find - поиск индекса записи,соответствующей строке// sTargetKey(если запись не найдена,возвращает-1)privateintFind(stringsTargetKey)for (intifi=0,-i<sKeys.Length;(String.Compare(sKeys[i],returni++)sTargetKey)==0)i,-}}return- 1 ;}// F i n d E m p t y - поиск с в о б о д н о г о// новой записиprivateintFindEmpty()for(intifi=(sKeys[i]0;i==<местаsKeys.Length;вмассиведляi++)null){return i;}}thrownewException("Массив/ / Ищем с о д е р ж и м о е//индексаторpublic object this[string sKey]{позаполнен");указаннойстроке-этоиестьset{466Часть VII.
Дополнительные главы//Проверяем,intifнетлитужеindex=Find(sKey);(index<0)такойстроки{// Е с л и н е т - ищем н о в о еindex = FindEmpty();sKeys[index]= sKey;место}// Сохраняем объект вoArrayElements[index]соответствующей= value;позиции}get{int indexif(index=<Find(sKey);0){returnnull;}return}oArrayElements[index];}}publicclassProgram{publics t a t i cvoidMain(string[]args){// Создаем массив с достаточным количествомK e y e d A r r a y ma = new K e y e d A r r a y ( 1 0 0 ) ;// Сохраняем возрастma [ " B a r t " ] = 8;та["Lisa"]= 10;та["Maggie"]= 2;членовсемьиэлементовСимпсонов/ / Ищем в о з р а с т L i s aConsole.WriteLine("ИщемвозрастLisa");i n t age =(int)ma["Lisa"];Console.WriteLine("Возраст Lisa {o}",age);// Ожидаем п о д т в е р ж д е н и я п о л ь з о в а т е л яConsole.WriteLine("Нажмите <Enter> для " +"завершения программы...");Console.Read();Класс K e y e d A r r a y в к л ю ч а е т д в а о б ы ч н ы х м а с с и в а .
М а с с и в o A r r a y E l e m e n t s содержит р е а л ь н ы е д а н н ы е K e y e d A r r a y . С т р о к и , к о т о р ы е х р а н я т с я в м а с с и в е s K e y s , работают в к а ч е с т в е и д е н т и ф и к а т о р о в м а с с и в а о б ъ е к т о в , г'-ый э л е м е н т s K e y s соответствуе т /-ой з а п и с и o A r r a y E l e m e n t s . Э т о п о з в о л я е т п р и к л а д н о й п р о г р а м м е и н д е к с и р о в а т ьK e y e d A r r a y с помощью индексов типа s t r i n g .Глава 20. Работа с коллекциями467Индексы, не являющиеся целыми числами, известны как ключи (key).
Кстати, можно реализовать K e y e d A r r a y с использованием L i s t < T > (см. г л аву 15, " О б о б щ е н н о е п р о г р а м м и р о в а н и е " ) вместо массива фиксированногоразмера. L i s t < T > индексируется, как и массив, так как они оба реализуютинтерфейс I L i s t (или I L i s t < T > ) . Это позволит сделать K e y e d A r r a yобобщенным классом и получить большую гибкость, чем при использовании внутреннего массива.Индексатор s e t [ s t r i n g ] начинает с проверки, не имеется ли уже данного индексав массиве, для чего он применяет функцию F i n d ( ) . Если она возвращает индекс,s e t []сохраняет новый объект данных в соответствующем элементе o A r r a y E l e m e n t s .
Ели F i n d ( ) н е может найти ключ, s e t [ ] вызывает F i n d E m p t y ( ) для возврата пустого элемента, где и будет сохранен переданный объект.Функция g e t [] работает с индексом с применением аналогичной логики. Сначала онаищет определенный ключ с использованием метода F i n d О . Если F i n d () возвращает неотрицательный индекс, g e t [] возвращает соответствующий член o A r r a y E l e m e n t s , гдехранятся запрошенные данные.
Если же F i n d О возвращает - 1 , то метод g e t [] возвращает значение n u l l , указывающее, что переданный ключ в списке отсутствует.Метод F i n d () циклически проходит по всем элементам массива s K e y s в поискахэлемента с тем же значением, что и переданное значение типа s t r i n g . Метод F i n d ( )возвращает индекс найденного элемента (или значение - 1 , если элемент не найден).Функция F i n d E m p t y () возвращает индекс первого элемента, который не имеет связанного ключевого элемента.При написании методов F i n d ( ) и F i n d E m p t y () не ставилась цель повысить ихэффективность, так что имеется множество возможностей сделать их быстрее, но все онине имеют никакого отношения к индексаторам.Правда, было бы здорово добавить возможность индексирования к классу связанногосписка L i n k e d L i s t ? Да, это можно сделать.
Но вспомните, что даже в классе K e y e d A r r a y требуется проход по массиву s K e y s для поиска определенного ключа, а значит,и функций F i n d () и F i n d E m p t y ( ) , которые этим занимаются. Точно так же при реализации индексатора для L i n k e d L i s t вам придется осуществлять проход по всему связанному списку, и единственный способ сделать это — пройти по всему списку с использованием итератора L i n k e d L i s t l t e r a t o r , следуя по ссылкам f o r w a r d от узла к узлу. Индексатор окажется удобным, но очень медлительным.Заметьте, что вы не можете удалять элементы посредством ключа n u l l .
Как же реализовать удаление? Как часто говорится в учебниках — "данная задача остается читателю в качестве домашнего упражнения".Функция M a i n () демонстрирует применение индексатора. Сначала программа создает объект та типа K e y e d A r r a y длины 100 (т.е. со 100 свободными элементами). Далее в этом объекте сохраняется возраст детей семьи Симпсонов с использованием именв качестве индексов. И наконец, программа получает возраст Лизы с применением выражения ma [" L i s a " ] и выводит его на экран.Обратите внимание, что программа должна выполнить преобразование типа для значения, возвращенного из та [ ] , так как K e y e d A r r a y написан таким образом, что можетхранить объекты любого типа. Без такого преобразования типов можно обойтись, еслииндексатор написан так, что может работать только со значениями типа i n t , или еслиK e y e d A r r a y — обобщенный класс (см.