1629295403-b876e2087bddebea4bc9666fb2377a02 (846199), страница 94
Текст из файла (страница 94)
Первое четное числоравно первому аргументу или, если он нечетен, на 1 меньше него. Последнее генерируемое четное число равно значению второго аргумента n S t o p (или, если n S t o p нечетно,на 1 больше него). Эта функция возвращает не значение типа i n t , а интерфейс I E n u m e r a b l e . Н о в ней все равно имеется инструкция y i e l d r e t u r n , которая возвращаетчетное число и затем ожидает очередного вызова из цикла f o r e a c h .Примечание:это еще один пример"коллекции",в основе которой нет никакой"настоящей" коллекции, наподобие уже рассматривавшегося ранее класса s t r i n g -цельные главыГлава 20.
Работа с коллекциями477C h u n k s . Заметим также, что эта коллекция вычисляется— на этот раз возвращаемыезначения не жестко закодированы, а вычисляются по мере необходимости. Это еще одинспособ получить коллекцию без коллекции. (Вы можете получать элементы коллекцииоткуда угодно — например, из базы данных или от Web-сервиса). И наконец, этот пример демонстрирует, что вы можете итерировать так, как вам заблагорассудится — например с шагом -2, а не стандартным единичным.Вот как можно вызвать D e s c e n d i n g E v e n s () из цикла f o r e a c h в функцииM a i n () (заодно здесь показано, что произойдет, если передать нечетные граничназначения — еще одно применение оператора %):// Инстанцирование класса "коллекции" EvenNumbersEvenNumbers en = new E v e n N u m b e r s ( ) ;// И т е р и р о в а н и е : выводим четные ч и с л а от 10 до 4C o n s o l e .
W r i t e L i n e ( " \ п П о т о к убывающих ч е т н ы х ч и с е л : " ) ;f o r e a c h ( i n t even in en.DescendingEvens(11,3)){Console.WriteLine(even);}Этот вызов дает список четных чисел от 10 до 4. Обратите также внимание, как используется цикл f o r e a c h .
Вы должны инстанцировать объект E v e n N u m b e r s (классколлекции). Затем в инструкции f o r e a c h вызывается метод именованного итератора:EvenNumbersforeach(inten = new E v e n N u m b e r s ( ) ;even in en.DescendingEvens(nTop,nStop))...Если бы D e s c e n d i n g E v e n s () был статической функцией, можно было быобойтись без экземпляра класса. В этом случае ее можно было бы вызватьс использованием имени класса, как обычно:foreach(int even(nTop,nStop))...inEvenNumbers.DescendingEvensПоток идей для потоков объектовТеперь, когда вы можете сгенерировать "поток" четных чисел таким образом, подумайте о массе других полезных вещей, потоки которых вы можете получить с помощьюаналогичных "коллекций" специального назначения: потоки степеней двойки, членоварифметических или геометрических прогрессий, простых чисел или чисел Фибоначчи — да что угодно.
Как вам идея потока случайных чисел (чем, собственно, и занимается класс R a n d o m ) или сгенерированных случайным образом объектов?Если вы помните демонстрационную программу P r i o r i t y Q u e u e из главы 15, "Обобщённое программирование", то можете взглянуть на другую демонстрационную п р о г р а м м у — P a c k a g e F a c t o r y W i t h l t e r a t o r — наприлагаемом компакт-диске. В ней проиллюстрировано использование блокаитератора для создания потока сгенерированных случайным образом объектов, представляющих пакеты. Для этого применяется та же функция, чтои в классе P a c k a g e F a c t o r y в демонстрационной программе P r i o r i t y Q u e u e , но содержащая блок итератора.478Часть VII.
Дополнительные главы IГлИтерируемые свойстваМожно также реализовать блок итератора в виде свойства класса.— конкретнее,в функции доступа g e t ( )свойства. Вот простой класс с о свойством D o u b l e P r o p .Функция доступа g e t () этого класса работает как блок итератора, возвращающий потокзначений типа d o u b l e :// P r o p e r t y l t e r a t o r - д е м о н с т р и р у е т р е а л и з а ц и ю функции// доступа get свойства класса как блока итератораclassPropertylterator{doublet]doubles = {1.0,2.0,3.5,4.67};// D o u b l e P r o p - с в о й с т в о " g e t " с блоком и т е р а т о р аpublicSystem.Collections.IEnumerableDoublePropget{foreach(doubledbindoubles){}}yieldreturndb;}}Заголовок D o u b l e P r o p пишется так же, как и заголовок метода D e s c e n d i n g E v e n s ( ) в примере именованного итератора.
О н возвращает интерфейс I E n u m e r a b l e , но в виде свойства, не использует скобок после имени свойства и имееттолько функцию доступа g e t ( ) , н о н е s e t ( ) . Функция доступа g e t ( ) реализована как цикл f o r e a c h , который итерирует коллекцию и применяет стандартную инструкцию y i e l dr e t u r n для поочередного возврата элементов и з коллекции чисел типа d o u b l e .Вот как это свойство можно использовать в функции M a i n ( ) :// Инстанцируем класс "коллекции" P r o p e r t y l t e r a t o rP r o p e r t y l t e r a t o r p r o p = new P r o p e r t y l t e r a t o r ( ) ;// Итерируем е е :генерируем значения типа double поforeach(double db in prop.DoubleProp)одному{Console.WriteLine(db);Вы можете использовать обобщенные итераторы.Подробнее оних можноузнать из справочной системы, из раздела, п о с в я щ е н н о г о п р и м е н е н и ю итераторов.Где надо размещать итераторыВ н е б о л ь ш и х к л а с с а х и т е р а т о р о в с п е ц и а л ь н о г о н а з н а ч е н и я в д е м о н с т р а ц и о н н о й программе I t e r a t o r B l o c k s к о л л е к ц и и р а з м е щ а л и с ь внутри класса итератора, к а к , например, в M o n t h D a y s .
В н е к о т о р ы х с л у ч а я х э т о в п о л н е к о р р е к т н о , н а п р и м е р , к о г д аколлекция п о х о ж а н а к л а с с S e n t e n c e C h u n k s , в о з в р а щ а ю щ и й ч а с т и т е к с т а , и л и D e s c e n d i n g E v e n s , который возвращает вычисляемые значения. Но что, если вы хотитеГлава 20. Работа с коллекциями479п р е д о с т а в и т ь и т е р а т о р , о с н о в а н н ы й н а б л о к е и т е р а т о р а д л я р е а л ь н о г о к л а с с а коллекции,например такого, как L i n k e d L i s t ?Эта задача решается в демонстрационной программе L i n k e d L i s t W i t h l t e r a t o r B l o c k на п р и л а г а е м о м к о м п а к т - д и с к е . В н е й класс L i n k e d L i s t пер е п и с а н и и с п о л ь з у е т м е т о д G e t E n u m e r a t o r ( ) , р е а л и з о в а н н ы й как блокитератора.О н п о л н о с т ь ю з а м е н я е т с т а р ы й классLinkedListlterator.В п р и в е д е н н о м д а л е е л и с т и н г е п р е д с т а в л е н а т о л ь к о н о в а я в е р с и я GetEnum e r a t o r ( ) .
П о л н о с т ь ю д е м о н с т р а ц и о н н у ю п р о г р а м м у м о ж н о найти н а прилагаемом компакт-диске.// L i n k e d L i s t W i t h l t e r a t o r B l o c k- реализует итератор для// связанного списка в виде блока итератораclass LinkedList//":I E n u m e r a t o r " больше не т р е б у е т с я{...Остальная//частьGetEnumeratorpublicкласса-реализованIEnumeratorкакблокитератораGetEnumerator(){//Проверяем//null,//установитьтак,//связанногоспискаондействительностьещеif(currentNodeне==текущегоиспользовался,чтобыонтакуказывалузла.чтонаЕслиегооннадоголовуnull){currentNode=head;}//Здесь//возвращаемоговыполняютсяитерацииметодомwhile(currentNode!=перечислителя,GetEnumerator()null){yield return currentNode.Data;currentNode = currentNode.forward;} }}Такой базовый вид блока итератора уже встречался ранее в этой главе:publicSystem.Collections.IEnumeratorGetEnumerator(){}Это выглядит точно так же, как и объект I E n u m e r a t o r , который метод G e t E n u m e r a t o r О возвращает в исходном классе L i n k e d L i s t .
Однако реализация методаG e t E n u m e r a t o r ( ) теперь работает совершенно иначе.Когда вы пишете блок итератора, С# создает для вас скрытый классL i n k e d L i s t l t e r a t o r . Вы не пишете этот класс и не видите его код.О н н е является частью демонстрационной программы L i n k e d L i s t WithlteratorBlock.В методе G e t E n u m e r a t o r ()вы просто используете цикл для обходавсех узлов связанного списка и возврата с помощью y i e l d r e t u r n элементов данных, хранящихся в каждом узле.
Этот код приведен в предыдущем листинге.480Часть VII. Дополнительные главыВам больше не нужно определять ваш класс коллекции, как реализующий I E n u m e r a t o r , что видно из приведенного в листинге заголовка класса.Все не так простоПри этом нельзя забывать о некоторых неизбежных вещах.Вы должны убедиться, что начинаете обход с начала списка.Для этого в новый класс L i n k e d L i s t добавлен член-данные c u r r e n t N o d e , посредством которого отслеживается перемещение итератора по списку.
Изначальночлен c u r r e n t N o d e равен n u l l , так что итератор должен проверять это условие.Если это так, он устанавливает c u r r e n t N o d e таким образом, чтобы тот указывална голову связанного списка.Если только h e a d н е равен n u l l (связанный список н е пуст), т о c u r r e n t N o d eстановится ненулевым до конца итераций. Когда же он достигает конца списка,итератор должен вернуть n u l l , что послужит сигналом о прекращении работыдля цикла f o r e a c h .При каждом шаге по списку необходимо осуществлять все действия, которые выполнялись ранее функцией M o v e N e x t () по перемещению к следующему узлу:// Действия, выполнявшиесяwhile(currentNode!= null){ранееMoveNextО// То, что делало с в о й с т в о C u r r e n ty i e l d r e t u r n c u r r e n t N o d e .
. . ; // ЧастьcurrentNode = currentNode.forward;кодаопущена}Большинство реализаций блоков итераторов используют цикл для прохода пок о л л е к ц и и — а иногда даже внутренний цикл f o r e a c h (но пример S t r i n g C h u n k s показывает, что это не единственно возможный путь).Когда вы проходите по списку и начинаете возврат данных с помощьюy i e l d r e t u r n , в ы должны "выковырять" хранящиеся данные и з объектаL L N o d e . Узел связанного списка — это всего лишь корзина для храненияs t r i n g , i n t , S t u d e n t и т.п.
объектов. Поэтому в ы должны вернуть н е c u r r e n t N o d e , а сделать следующее:y i e l d r e t u r n c u r r e n t N o d e . D a t a ; / / ВотcurrentNode = currentNode.forward;теперьверноTo же, но за сценой, делает и исходный перечислитель. Свойство D a t a классаL L N o d e возвращает данные в узле как O b j e c t . Исходный необобщенный связанный список преднамеренно спроектирован обобщенным настолько, насколькоэто возможно, поэтому он и хранит объекты класса O b j e c t .Теперь цикл w h i l e с инструкцией y i e l d b r e a k выполняет то, что ранее в ы должныбыли делать с огромным количеством работы. В результате метод G e t E n u m e r a t o r () работает в цикле f o r e a c h в функции M a i n ( ) , как и ранее.Если вы немного поразмышляете над этим, то поймете, что такая реализация просто перемещает функциональность старого класса итератора L i n k e d L i s t l t e r a t o r в классLinkedList.Глава 20.