Нэш Трей - C# 2010. Ускоренный курс для профессионалов (2010) (1160865), страница 73
Текст из файла (страница 73)
Хотя это правило не является незыблемым и следовать ему не обязательно, в нем есть определенный семантический смысл. Применение методов обычно указывает на то, что над типом выполняются некоторые операции, результат которых ожидается. С другой стороны, доступ через свойства указывает на то, что требуется напрямую получить доступ к внутреннему состоянию самого объекта. Именно поэтому данное эмпирическое правило имеет хороший семантический смысл. Аналогичное семантическое разделение имеет смысл применять ко всем свойствам и методам внутри создаваемых типов.
Итераторы В предыдущем разделе приводился краткий и легковесный пример создания пере- числителя для типа коллекции. После того, как вы сделаете это несколько раз, подобная задача покажется рутинной. А всякий раз, когда такое происходит, людям свойственно допускать досадные ошибки. Для облегчения задачи в СФ предлагается новая конструкция, называемая блоком итератора. Прежде чем погрузиться в детальные исследования итераторов, давайте быстро взглянем, как с помощью итератора решить задачу, рассмотренную в качестве примера в предыдущем разделе. Это будет именно тем "легким способом", о котором упоминалось ранее.
эз1лд зузгело сз1лд зузгет.Со11есС1олз~ озалом зузгеа.Со11есг1ооз.пелегас; Массивы, типы коллекций и итерэторы 273 рпЬ1зс с1аяя МуСо11<Т>: 1ЕповетаЬ1е<Т> ( роЬ11с МуСо11( Т[] Ттевя ) ( тЫя.згевя = Тгевя; ) рпь1зс 1еппвегагог<т> сетеппвегагог О ( готеасЬ( Т Етев 1п зтевя ) ( узе1С тетстп зтевз ) ) 1Еппвегатог 1ЕпиветаЫе.сетЕппветатот О ( гетега СетЕпсвегатог()з ) рг1нате Т[] зтевяз ) риЫзс с1аяя Епттурозпт ( ятатзс ноты Мазп() ( мусо11<зпг> зптечетя = пея мусо11<1пт>( пеи зпг[] (1, 2, 3, 4) Гогеасц( зпт и зп Тптеяетя ) ( Сопяо1е.иг1теьзпе( и )з ) Вряд ли что-то может быть проще.
Обратите внимание. что реализация перечислителя из примера в предыдущем разделе теперь сведена к трем строкам внутри метода Еетеппветатот. Секрет кроется в ключевом слове уз.е16. Наличие этого ключевого слова определят блок кода как у].е1<(-блок. Поначалу догадаться о том, что в этом блоке происходит, довольно непросто. При вызове ОетЕппвегатот код в методе, содержащем оператор уте16. в этот момент времени не выполняется. Взамен компилятор генерирует класс перечислителя, который содержит уз.е1б-блок.
Экземпляр этого класса и возвращается. Таким образом, когда оператор Тотеасп в методе маьп вызывается через методы 1Еппветатот<Т>,используется код блока уье1<(. В атом коде пока что пропущена одна вещь, которая присутствует в примере из прельздущего раздела — синхронизация. Давайте посмотрим, как добавить синхронизацию к перечислителю, возвращенному блоком уз.е1<(. Ниже приведена замена предыдущего метода СетЕпсветатот: роь11с 1еповегагог<т> пегеповегатог() ( 1ос)г( 1тевя.зупскоот ) ( Тот( зпт з = Оз 1 < 1гевя.ьепдтьз еез ) ( узе1г( тегцтп 1тевя[1]з На удивление просто, не правда лиу Для примера способ итерации по коллекции бьп изменен — вместо Тотеасп используется цикл Тот.
Теперь рассмотрим, что за "магию" здесь совершает компилятор. Как и раньше, блок кода у1е1к( не выполняется немедленно. Вместо этого возвращается обьект перечислителя. Внутренне перечислитель может находиться в одном из нескольких состояний. При первом вызове Монеыехт на пере- числителе блок кода выполняется до достижения первого оператора уье1П. Каждый последующий вызов монеыехт продолжает выполнение цикла либо до достижения оператора узе1б Ьтеа)г, либо когда цикл дойдет до конца метода.
Как только это случится, перечислитель попадает в финальное состояние, и применять его для дальнейшего перечисления коллекции уже нельзя. Фактически метод ее яет не доступен для использо- 274 Глава 9 ванна на перечислителе, сгенерированного блоком у1е1с), и в случае его вызова будет сгенерировано исключение МаГЯпррагсеаехсерсуоп. В конце перечисления, как ожидалось, выполняется любой блок 11па11у, который может быть внутри блока у1е1с(. В данном случае это означает снятие блокировки, гюскольку оператор 1ас)с "за кулисами" превращается в конструкцию Сгу/11гса11у. К тому же, если перечислитель будет освобожден до конца цикла, то компилятор достаточно интеллектуален, чтобы поместить код внутрь блока 11па11у в реализацию 01зраэе перечислителя, обеспечивая снятие блокировки. Как видите, при использовании итераторов компилятор делает за вас массу незаметной работы.
В качестве финального примера рассмотрим еще один способ итерации по элементам этой коллекции: рпьгзс 1епппегагаг<т> сесепппегагаг( ьаа1 зупсьгап1хеа ) 11( эупснгапвгеа ) ( Мопгсаг.Епсег( Ьсетэ.зупспаас )> ) сгу ( Ьпс 1паех = Ов нб11е( Ггпе ) ( 11( 1паех < 1гехсз.Ьепчсп ) ( ууе1а гегпгп Тсетз[ьпаех+ь); ) е1зе ( уге1а Ьгеак; ) 11па11у ( 11( эупспгап1геа ) мапггог.ех1с( 1гесхз.яупскааг )в ) ) ) рпЬ|ьс 1Епппегасаг<Т> Еескппвегасаг() ( гесогп Сесвпптегасаг( Га1эе ); ) Зто не слишком изящный способ итерации по элементам, и он служит лишь примером использования оператора у1е1с( Ьгеа)с.
Обратите внимание, что был создан новый метод еегепшпегагаг. принимающий признак ьоа1, который указывает на то, какой перечислитель желает получить вызывающий кад: синхронизированный или несинхронизированный. Здесь важно отметить, что объект перечислителя, созданный компилятором, теперь имеет общедоступное пале по имени зупспгоп1гес). Любые параметры, которые переданы методу, содержащему блок ууе1с), добавляются в виде общедоступных полей в сгенерированный класс перечислителя. На заметку! Поскольку перечислитель, сгенерированный из блока ууе1а, захватывает локальные переменные и параметры, объявлять параметры ге г и апс в методах, реализующих блок ууе16, не разрешено. Можно привести довод в пользу того, что добавленные поля должны быть приватными, а не общедоступными, посколъку если обратиться к этим общедоступным полям и модифицировать нх во время перечисления, то можно разрушить перечислитель.
В данном случае, если модифицировать поле эупсЬгапагес) во время перечисления и присвоить ему Еа1зе, то другие сущности никогда не смогут получить доступ к коллек- Массивы, типы коллекций и итерзторы 275 ции, поскольку блокировка никогда не будет снята. Несмотря на то что для доступа к общедоступным полям перечислителя, сгенерированного из блока у1е1<], должна использоваться рефлексия, очень легко и опасно сделать это некорректно. Это вовсе не означает, что такая техника не может быть полезной; польза от нее будет доказана в примере, приведенном в разделе "Прямые, обратные и двунаправленные итераторы", где продемонстрируется создание двунаправленного итератора.
Чтобы избежать споров. можно ввести пресловутый дополнительный уровень посредничества. Вместо возврата результирующего перечислителя из блока у1е1<] этот перечислитель помещается внутрь оболочки, которая и будет возвращаться. Данный подход показан в следующем примере: ся1пд Яуякепт сятпд Яуякеп.тьгеят]тпдт сягпд Яуякеп.пе11еск1опт свьпд Яуякеп.Со11есггопвт ив1пд Яувсел.со11есктопя.Яепег1ст рчь11с с1алв йпсеигарраг<т>: 1епсвегаеог<т> роЬ11с Еппиигаррег( 1йпсвегасог<Т> 1ппег ] ( еьья.гппег — ыжегт ] рттЬ11с чо1т) П1зрозе() ( 1ппег.вьзрове(); роЬ11с Ьоо1 Мочвйехс() ( гессгп 1ппег.иочеиахс(] т ) р»Ь11с чо1<] Велес() ( 1ттпег.Вовек() т ] р»Ь11с Т Спггепс ( дее ( геспгп 1ппег.Спггапст ) оЬЬяесс 1Ешлзегасог.Спггепс ( дес ( геспгп 1ппег.Спггепст ) ргэчасв 1йпсэжгасог<Т> 1ппег; ) риЬ1гс с1звв МуСо11<Т>: 1ЕпялегзЬ1е<Т> ( ряЬ1(с МуСо11( Т() )Селя ] ( Сйьв.1селв = 1сезят ряпьгс 1Епсаегясог<Т> яеГЕппаегасог( Ьоо1 яупспгоптяет] ) геспгп( пе» Епсиигарраг<Т>(десрг1»асейпоеегасог(лупсьгоп1зеб]) ) ) рг1ттасе 1йпплжгасог<Т> Оегрг1»асейпчиегасог( Ьоо1 зупсЬгопззеб ) ( 1Т( лупсьгоп1вет) ) ( Моп1сог.Епгаг( 1зжаз.вупсаоос )т ) с*у ( Тогеась( Т 1сеп 1п 1сепв ) ( у1е1с гессгп 1савт ) Тьпа11у ( 1Т( вупсЬгопйзеб ) ( мопйеог.ехйе( Зевав.зупсйоое )т ] ] 276 Глава 9 рпЬ1гс 1Еппвегягог<Т> СеСЕппвегзьог() ( гегпгп Сегвппвегятог( Та1яе ); ) 1Еппвегагог 1ЕппвегнЬ1е.оеСЕппвегягог О ( гегпгп СеСЕппвегагог()к ) рггчяге Т(] ггевяп рпЫ1с с1яяя Епггуро1пС ( ягяг1с по1О Маги() МуСо11<гпг> 1пгесегя = пен Мусо11<тпг>( пен Тпг[] (1, 2, 2, Ч] ); 1Еппвегагог<1пС> еппвегагог = 1пгеоегя.сеСЕппвегясог( Сгпе )! // Получить ссылку нз поле яупспгоп1гес.
// Генерирует исключение!!! оЬ)есг 11е1О = еппвегзгог.сеСТуре(). СеСР)е1О("яупспгоп1гес").Сегча1пе( еппвегягог )и ) В Ма].п показано, как зловредный разработчик может попытаться изменить состояние перечислителя во время перечисления. Как видите, предпринимается попытка изменить значение свойства в перечислителе с помощью рефлексии. Это приведет к генерации исключения, поскольку данное свойство в оболочке не существует в обертке. На заметку! Те из вас, кто знаком с хитросплетениями рефлексии, знают, что для кода технически возможно модифицировать приватное поле внутри экземпляра еппвкгаррег<Т>.
Однако для этого код должен отвечать требованиям безопасности еее1есгсопрегв1яя1оп доступа к коду (собе асеева весоп(у — СЯЗ). Лицо, запустившее этот код, должно получить соответствую. щие полномочия явным образом, что маловероятно. Описание САЗ не входит в перечень тем этой книги, но все тонкие детали САБ, включая способы расширения его в соответствие с существующими потребностями, можно найти в книге Брайана А. Ла-Маччиа (Впал А. СаМассща) .'г(ЕТ Ргзтен(у(г Бесигду (Реагзоп Ебоса(!оп, 2002 г.). До сих пор было показано, как применять блоки итератора для создания перечислителей. Однако их также можно использовать для генерации перечислимого типа. Например, предположим, что нужно выполнить итерацию по первым нескольким степеням двойки.











