ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 11.12.2025

Просмотров: 3206

Скачиваний: 1

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

Это исключительно простой "класс коллекции", основанный на массиве, как и клак K e y e d A r r a y . Класс содержит массив, элементы которого имеют тип s t r i n g . Ков клиент итерирует данную коллекцию, ее блок итератора выдает ему эти строки по одной Каждая строка содержит имя месяца с количеством дней в нем. Тут нет ничего сложной, Класс определяет собственный блок итератора, в данном случае как метод GetEnumera­ t o r ( ) . Метод G e t E n u m e r a t o r ( ) возвращает объект типа S y s t e m . C o l l e c t i o n s ,

I E n u m e r a t o r . Да, вы должны были писать такой метод и ранее, но вы должны были писать не только его, но и собственный класс-перечислитель для поддержки вашего класса-коллекции. Теперь же вы пишете только простой метод, возвращающий пере. числитель с использованием новых ключевых слов y i e l d r e t u r n . Все остальное C# делает за вас: создает класс-перечислитель и применяет его метод M o v e N e x t () для итерирования. У вас уменьшается количество работы и размер исходного текста.

Ваш класс, содержащий метод G e t E n u m e r a t o r ( ) , больше не должен реализовы вать интерфейс I E n u m e r a t o r . В следующих разделах вам будет показано несколько вариаций блоков итераторов:

обычные итераторы; именованные итераторы;

свойства классов, реализованные как итераторы.

Что такое коллекция

Остановимся на минутку и сравним эту небольшую коллекцию с коллекцией L i n k e d L i s t , рассматривавшейся ранее в главе. В то время как L i n k e d L i s t имеет сложную структуру узлов, связанных посредством указателей, приведенная простейшая коллекция месяцев основана на простом массиве с фиксированным содержимым. Но все же следует расширить понятие коллекции.

(Ваш класс коллекции не обязан иметь фиксированное содержимое — большинство коллекций разработаны для хранения объектов путем добавления их в коллекции, на­ пример, с помощью метода A d d () или чего-то в этом роде. Класс K e y e d A r r a y , к при­ меру, использует для добавления элементов в коллекцию индексатор. Ваша коллекция также должна обеспечивать метод A d d ( ) , как и блок итератора, чтобы вы могли рабо­ тать с ней с помощью f o r e a c h . )

Цель коллекции, в наиболее общем смысле, заключается в хранении множества объ­ ектов и обеспечении возможности их обхода, последовательно выбирая их по одномухотя иногда может использоваться и произвольная выборка, как в демонстрационной программе I n d e x e r . (Конечно, массив и так в состоянии справиться с этим, без допол­ нительных "наворотов" наподобие класса M o n t h D a y s , но итераторы вполне могут при­ меняться и за пределами примера M o n t h D a y s . )

Говоря более обобщенно, независимо от того, что именно происходит за сценой, ите­ рируемая коллекция генерирует "поток" значений, который можно получить с помощью f o r e a c h .

Для лучшего понимания данной концепции ознакомьтесь с еще одним примером про­ стого класса из демонстрационной программы I t e r a t o r B l o c k s , который иллюстри­ рует чистую идею коллекции:

/ / S t r i n g C h u n k s - о п р е д е л е н и е и т е р а т о р а , в о з в р а щ а ю щ е г о / / ф р а г м е н т ы т е к с т а

474

Часть VII. Дополнительные главы


c l a s s S t r i n g C h u n k s

/ / G e t E n u m e r a t o r - и т е р а т о р . О б р а т и т е в н и м а н и е , к а к о н / / ( д в а ж д ы ) в ы з ы в а е т с я в M a i n

p u b l i c S y s t e m . C o l l e c t i o n s . I E n u m e r a t o r G e t E n u m e r a t o r ( )

{

/ / В о з в р а т р а з н ы х ф р а г м е н т о вт е к с т а н а к а ж д о й и т е р а ц и и

y i e l d

r e t u r n

" U s i n g

i t e r a t o rи /.

y i e l d

r e t u r n

" b l o c k s

" ;

 

y i e l d

r e t u r n

" i s n ' t

a l l

" ;

y i e l d

r e t u r n

" t h a t

h a r d " ;

y i e l d

r e t u r n

" . " ;

 

 

}

Коллекция S t r i n g C h u n k s , как ни странно, ничего не хранит в обычном смысле этого слова. В ней нет даже массива. Так где же тут коллекция? Она — в последователь­ ности вызовов y i e l d r e t u r n , использующих специальный новый синтаксис для воз­ врата элементов один за другим, пока все они не будут возвращены вызывающей функ­ ции. Эта коллекция "содержит" пять объектов, каждый из которых представляет собой простую строку, как и в рассмотренном только что примере M o n t h D a y s . Извне класса, в функции M a i n ( ) , вы можете итерировать эти объекты посредством простого цикла

f o r e a c h ,

поскольку конструкция y i e l d

r e t u r n возвращает по одной

строке за раз.

Вот часть

функции

M a i n ( ) ,

в

которой выполняется итерирование

"коллекции"

S t r i n g C h u n k s :

 

 

 

 

 

/ / И т е р и р у е м к о л л е к ц и ю с т р о к

 

 

S t r i n g C h u n k s s c

= n e w S t r i n g C h u n k s ( ) ;

 

/ / И т е р и р у е м - в ы в о д и м т е к с т ,

п о м е щ а я к а ж д ы й ф р а г м е н т

/ / в с в о е й с о б с т в е н н о й с т р о к е

 

 

C o n s o l e . W r i t e L i n e ( " \ С т р о к и : \ п " ) ;

 

f o r e a c h

( s t r i n g

s C h u n k

i n

s c )

 

 

{

 

 

 

 

 

 

C o n s o l e . W r i t e L i n e ( s C h u n k ) ;

Синтаксис итератора

В C# 2.0 вводятся два новых варианта синтаксиса итераторов. Конструкция y i e l d r e t u r n больше всего напоминает старую комбинацию M o v e N e x t () и C u r ­ r e n t для получения очередного элемента коллекции. Конструкция y i e l d b r e a k по­ хожа на оператор b r e a k , который позволяет прекратить работу цикла или конструкции s w i t c h .

yield return

Синтаксис y i e l d r e t u r n работает следующим образом.

1.

При первом вызове он возвращает первое значение коллекции.

 

2.

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

»

3.

И так далее...

 

Глава 20.

Работа с коллекциями

475

 


Это очень похоже на старый метод итератора M o v e N e x t ( ) , использовавшийся в ко| де L i n k e d L i s t . Каждый вызов M o v e N e x t () предоставляет новый элемент коллекции Однако в данном случае вызов M o v e N e x t () не требуется.

Что же подразумевается под очередным вызовом? Давайте еще раз посмотрим на цикл f o r e a c h , использующийся для итерирования коллекции S t r i n g C h u n k s :

f o r e a c h

( s t r i n g s C h u n k i n s c )

{

C o n s o l e . W r i t e L i n e ( s C h u n k ) ;

}

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

f o r e a c h итератор возвращает следующий элемент коллекции.

yield break

Следует упомянуть еще об одном синтаксисе. Можно остановить работу итератора в определенный момент, использовав в нем конструкцию y i e l d b r e a k . Например достигнут некоторый порог при тестировании определенного условия в блоке итератор класса коллекции, и вы хотите на этом прекратить итерации. Вот краткий пример блока итератора, использующего y i e l d b r e a k именно таким образом:

/ / Y i e l d B r e a k E x - п р и м е р и с п о л ь з о в а н и я к л ю ч е в о г о с л о в а / / y i e l d b r e a k

c l a s s Y i e l d B r e a k E x

{

i n t [ ] p r i m e s = { 2 , 3 , 5 , 7 , 1 1 , 1 3 , 1 7 , 1 9 , 2 3 } , -

/ / G e t E n u m e r a t o r - в о з в р а т п о с л е д о в а т е л ь н о с т и п р о с т ы х / / ч и с е л с д е м о н с т р а ц и е й п р и м е н е н и я к о н с т р у к ц и и y i e l d / / b r e a k

p u b l i c S y s t e m . C o l l e c t i o n s . I E n u m e r a t o r G e t E n u m e r a t o r ( )

{

f o r e a c h ( i n t n P r i m e i n p r i m e s )

{

i f ( n P r i m e >

y i e l d r e t u r n

}

}

}

1 3 ) y i e l d b r e a k ; / / Н о в ы й с и н т а к с и с n P r i m e ;

В рассмотренном случае блок итератора содержит оператор if, который проверяет простые числа, возвращаемые итератором (кстати, с применением еще одного цикла f o r e a c h внутри итератора). Если простое число превышает 13, в блоке выполняется инструкция y i e l d b r e a k , которая прекращает возврат простых чисел итератором. В противном случае работа итератора продолжалась бы, и каждая инструкция y i e l d r e t u r n давала оче­ редное простое число, пока коллекция полностью не исчерпалась бы.

Блоки итераторов произвольного вида и размера

До этого момента блоки итераторов выглядели примерно следующим образом:

p u b l i c

S y s t e m . C o l l e c t i o n s . I E n u m e r a t o r G e t E n u m e r a t o r ( )

{

 

476

Часть VII. Дополнительные глава


y i e l d

r e t u r n s o m e t h i n g ;

}

 

Однако

они могут также принимать и другие форм ы — именованных итераторов

и свойств

классов.

Именованные итераторы

Вместо того чтобы писать блок итератора в виде метода с именем G e t E n u m e r a t o r ( ) ,

можно написать именованный итератор— функцию, возвращающую интерфейс

S y s t e m .

C o l l e c t i o n s . I E n u m e r a b l e вместо I E n u m e r a t o r , которая н е обязана

иметь имя

G e t E n u m e r a t o r () — можете назвать ее хоть M y F u n c t i o n ( ) .

 

Вот, например, простая функция, которая может использоваться для итерирования четных чисел от некоторого значения в порядке убывания до некоторого конечного зна­ чения — да, да, именно в порядке убывания: для итераторов это сущие пустяки!

/ / E v e n N u m b e r s - о п р е д е л я е т и м е н о в а н н ы й и т е р а т о р , к о т о р ы й / / в о з в р а щ а е т ч е т н ы е ч и с л а в о п р е д е л е н н о м д и а п а з о н е в / / п о р я д к е у б ы в а н и я

c l a s s E v e n N u m b e r s

{

/ / D e s c e n d i n g E v e n s - э т о " и м е н о в а н н ы й и т е р а т о р " , в к о т о р о м / / и с п о л ь з у е т с я к л ю ч е в о е с л о в о y i e l d b r e a k . О б р а т и т е

/ / в н и м а н и е н а е г о и с п о л ь з о в а н и е в ц и к л е f o r e a c h в ф у н к ц и и

/ / M a i n ( )

 

p u b l i c

S y s t e m . C o l l e c t i o n s . I E n u m e r a b l e

 

D e s c e n d i n g E v e n s ( i n t

n T o p ,

{

i n t

n S t o p )

 

 

/ / Н а ч и н а е м с б л и ж а й ш е г о к n T o p ч е т н о г о ч и с л а , н е

/ / п р е в о с х о д я щ е г о

е г о

i f ( n T o p % 2 ! = 0 )

/ / Е с л и n T o p н е ч е т н о

n T o p - = 1 ;

 

/ / И т е р а ц и и о т n T o p в п о р я д к е у м е н ь ш е н и я д о б л и ж а й ш е г о к / / n S t o p ч е т н о г о ч и с л а , п р е в о с х о д я щ е г о е г о

f o r

( i n t i = n T o p ;

i > = n S t o p ; i - = 2 )

i f

( i

< n S t o p )

y i e l d b r e a k ;

/ / В о з в р а щ а е м о ч е р е д н о е ч е т н о е ч и с л о н а к а ж д о й

/ / и т е р а ц и и

 

y i e l d

r e t u r n i ;

 

}

}

Метод D e s c e n d i n g E v e n s () получает два аргумента (удобная возможность), опре­ деляющих верхнюю и нижнюю границы выводимых четных чисел. Первое четное число равно первому аргументу или, если он нечетен, на 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. Работа с коллекциями

477


C 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 ()

(заодно

здесь показано, что произойдет, если

передать нечетные

гранична

значения — еще одно применение оператора %):

 

 

/ / И н с т а н ц и р о в а н и е к л а с с а " к о л л е к ц и и " E v e n N u m b e r s

 

E v e n N u m b e r s e n = n e w E v e n N u m b e r s ( ) ;

 

 

/ / И т е р и р о в а н и е : в ы в о д и м ч е т н ы е ч и с л а о т 1 0 д о 4

 

C 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

e v e n i n e n . D e s c e n d i n g E v e n s ( 1 1 , 3 ) )

 

{

C o n s o l e . W r i t e L i n e ( e v e n ) ;

}

Этот вызов дает список четных чисел от 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 вызывается метод именованного итератора:

E v e n N u m b e r s e n = n e w E v e n N u m b e r s ( ) ;

 

 

f o r e a c h ( i n t e v e n

i n e n . D e s c e n d i n g E v e n s ( n T o p ,

n S t o p ) )

. . .

Если бы D e s c e n d i n g E v e n s () был статической функцией, можно было бы

обойтись без экземпляра класса. В этом случае ее

можно было

бы вызвать

с использованием имени класса, как обычно:

 

 

f o r e a c h ( i n t

e v e n i n E v e n N u m b e r s . D e s c e n d i n g E v e n s

( n T o p , n S t o p ) ) . . .

Поток идей для потоков объектов

Теперь, когда вы можете сгенерировать "поток" четных чисел таким образом, поду­ майте о массе других полезных вещей, потоки которых вы можете получить с помощью аналогичных "коллекций" специального назначения: потоки степеней двойки, членов арифметических или геометрических прогрессий, простых чисел или чисел Фибонач­ чи — да что угодно. Как вам идея потока случайных чисел (чем, собственно, и занимает­ ся класс 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 Гл