Нэш Трей - C# 2010. Ускоренный курс для профессионалов (2010) (1160865), страница 131
Текст из файла (страница 131)
метод тгапягогы преобразован в расширяющий метод. Но это не все! Можно также воспользоваться обобщениями и сделать код в большей степени повторно используемым. Однако и это еще не все! Вдобавок можно применить итераторы и заставить метод т ганя го па вычислять элементы в "ленивой" манере. Ниже показана версия тгапягоги с большей степенью повторного использования; ояапо Яуясеи; ояапр Яузгеи.ьтпйт ояапд Яуягеы.Со11ессаопя.Оепег1с; роЫЬс ясас1с с1аяя муехгепятопз ( роь11с ягаг1с 1епптегаые<В> тгапягогт<т, е>( гыз 1еппиегаь1е<т> Ьпрог, Еопс<Т Е> ор ) ( Гогеась( чаг Ьгеы Ьп гпрчс ) ( у1е1б гегогп ор( Ьгеы )т ) ) ) рпЬ11с с1аяя ТгапятогыЕхаир1е ( ягагас боиЫе ОЬч16еВуТЬгее( 1пг и ) ( гегогп (бооЬ1е)п / 3; ) ягаг1с чогб Магп() ( чаг ЬпСЬЬяг = пен Ьазг<гпг>() ( 1, 2, 3, 4, 5 ); // Вычислить новый список.
чаг бопЫеьаяг = 1псьтзг.тгапягопя( ОгчабеВутпгее )т Гогеасп( чаг Ьсеи гп ЬпГШяг ) Сопзо1е.игагеЬгпе( Ьсет )т Сопзо1е.иг1сеьтпе()т // Отобразить новый список. гогеась( чаг Ьгеи Ьп Йопыеьтяг ) ( Сопяо1е.кгтгеЬЬпе( Ьгеи ); ) 488 Глава (4 Совсем другое дело! Для начала обратите внимание, что Тгзпзуогп<Т> стал обобщенным расширяющим методом. Более того, он принимает и возвращает экземпляры типа 1ЕиитегаЬ1е<Т>. Теперь Тгапяуога<Т> может использоваться в любой обобщенной коллекции и принимать делегат, описывающий способ трансформации каждого элемента.
Тип ропе<> определен в пространстве имен яуягет и он облегчает объявление делегатов. Поскольку для возврата элементов из Тгаияуопз<Т> посредством ключевого слова у1е1с( применяется блок итератора, каждый элемент обрабатывается только тогда, когда курсор возвращенного типа 1ЕпппегаЬ1е<Т> перемещается вперед. В этом примере зкономия вычислений незначительна, но такого рода ленивые вычисления являются одним из краеугольных камней языка ЫХЯ.
Довольно легко представить себе ситуацию, когда переданная операция может достаточно долго выполнять обработку каждого элемента входной коллекции. Входная коллекция может содержать длинные строки, и операция, например, может заниматься их шифрованием. Другая причина удобства применения "ленивого" вычисления состоит в том, что входная коллекция может представлять собой бесконечную последовательность.
Каким образом? Взгляните на следующий пример, где также включен небольшой анонс лямбда-выражений, которым посвящена глава 15: пя1по Яуягегп азгпу Яуягет.11пЧ) пв1па Яузгеа.со11есС1опя.бепег1с! риЬ11с згвС1с с1аяз МуЕхгепятопя ( рпь11с ясас1с 1еиптегаь1е<е> тгапягогт<т, к>( Сбтя 1Еппаегабае<Т> Тпрзг, Еиис<Т, К> ор ) ( Тогезсп( чаг 1Сеа Тп тиров ) ( у1е10 гегпгп ор( 1Сеа ); ) ) роЬ11с с1зяя ТгапягогаЕхатр1е ( всас1с 1епзаегяь1е<1ис> сгеасе1п11пгсеяегтев() ( 1пс п = О! чд11е ( Сгие ) ( у1е1О гегпгп и+е; вгагтс чо1с( Мати () ( чаг 1и11п1сезег1ея1 = Сгеаге1пггп1сеЯеггев()! чаг 1а21пзеевег1ев2 = 1п21вьеевег1ея1.тгапяЕоюв( к => (бооЬ1е)к / 3 ) 1епиаегасог<оопь1е> Тсег = 1и11п1сезег1ев2.6есвпптегзгог()! Гог( гпг 1 = 0; 1 < 25! ++1 ) ( тгег.Мочекехг()! Сопяо1е.нгггещпе( 1Сег.СоггепС Насколько это здорово? С помощью блока итератора очень легко создавать бесконечные последовательности.
разумеется, для циклов нельзя использовать ТогеасЬ, Расширяющие методы 489 иначе программа никогда ие завершится, и придется прекращать ее принудительно. Забавный синтаксис внутри вызова метода Тгапзгогш<Т> — это лямбда-выражение. Используемое подобным образом лямбда-выражеиие определяет функцию, которая в данном случае передается в виде делегата. Лямбда-выражеиия можно воспринимать как сжатый синтаксис определения анонимных методов. В главе 15 лямбда-выражеиия рассматриваются более подробно. Применяемые показанным выше способом расширяющие методы позволяют реализовать более функциональный стиль программирования'.
В конечном счете, только что показанный метод Тгапзуогш<т> попадает в эту натегорию. Фактически большая часть нововведений, появившихся в С№ 3.0, направлены иа облегчение использования парадигмы функционального программирования. К этим средствам относятся расширшощие методы, лямбда-выражения и язык Ы)чь). Каждое из этих средств делает упор иа вычислительную операцию, а ие иа структуру вычисления.
Преимущества функционального программирования многочисленны, и им можно было бы посвятить целую книгу. Например. функциональное программирование облегчает параллелизм, поскольку переменные обычно никогда ие изменяются после начального присваивания; в результате требуется меньше блонировок синхронизации. На заметку! Разработчики С++, знакомые с шаблонным метапрограммированием, которое описано в блестящей книге Дзвида Абрахамса (Сач)С АЬгалатз) и Алексея Гуртового (А)ехэеу Воцочоу) С++ Тетр1ате Ме(аргодгатт(лд; Сопсергз, Тоо)з, апг) Тесйпгццеэ Ггот Вооз( апг(Веуолгт(Абб(зопУчез!еу Рготевзюпа), 2004 г.], чувствуют себя буквально как дома, когда идет речь об этом стиле функционального программирования.
В действительности шаблонное метапрограммирование предоставляет относительно бедную среду функционального программирования, поскольку как только переменной присвоено значение (или символ — в терминологии функционального программирования), оно больше никогда не меняется. С другой стороны, С№ предлагает гибридную среду, в которой вы вольны реализовать функциональное программирование, если того желаете. Вдобавок те, кто знаком со стандартной библиотекой шаблонов (БТЬ), испытают похожее чувство от этого стиля программирования. Библиотека БТ1 распространилась в сообществе программистов С++ в начале 90-х годов, и своим появлением стимулировала образ мышления, более склоняющийся в сторону функционального программирования.
Цепочки операций С использованием расширяющих методов построение цепочек становится более естественным процессом. Здесь иет ничего такого, чего нельзя было бы сделать с помощью С№ 2.0, применяя обычные статические и анонимные методы. Однако благодаря упрощенному синтаксису, связывание операций в цепочки исключает излишнюю суету и может стимулировать новаторское мышление.
Начнем с примера из предыдущего раздела, в котором список целых чисел трансформировался в списон действительных чисел двойной точности. На этот раз посмотрим, как можно плавно связывать операции в цепочки. Предположим, что после деления целых чисел иа 3 необходимо вычислить квадрат результата. В следующем коде показано, как это можно сделать. цз1пс Зузгеяо цззлд Эузтек.01пгц цз1ло Яузтек.со11ес11опз.сепег1сг Основные понятия функционального программирования можно узнать по адресу Ьгкр: 1/ гц.н1К1реб1з.огд/н1К1/Функциональное программирование или в соответствующей литературе. 490 Глава!4 ягагтс с1аяя муехтепя1опя РиЬ11с риЬ 1тс ятатбс 1ЕпиветаЬ1е<В> Тгаиятогв<Т, В>( Гйуя 1ЕпивегаЬ1е<Т> гирст, Еипс<Т, В> ор ) ( Тогезсп( чаг 1тев тп гирст ) ( уте1б гетигп ор( 1тев )) ) ) ) ривутс ( яга с1аяя ТгапяуогвЕхавр1е Г1с 1ЕиивегзЬ1е<1пт> Сгеате1п11пттеЕТзт() ( тпг п = О; хЬ11е( Ггие ) уте1б гегитп иээ) ятаттс бсивае О1ч1беВутпгее( тпт п ) ( гетсгп (бсиЬ1е)и / 51 ятаттс бсиЪ1е 5Чизге( боиЬ1е г ) тегигп т * г; ) ятаттс чс1б Мати() чаг бтчгбеВуТЬгее = пез еипс<1иг, боиЬ1е>( Отч1беВутпгее ); чаг ячиагенивЬег = пех еипс<бсиЬ1е, бсиЬ1е>( Бчсаге ); чаг гези1С = СгеагаХпТ1п1геЕ1ее().
Тгапзаогв( бхч1баВТТЬгее ) . Тгапзвогв( зциагаиитЪег ); чат 1тег = геяи1Г.СеГЕиивегатог () 1 Тот( Тпг т = О; т < 251 ээ1 ) ( 1тег.МочеИехт(); Сопяо1е.игтте11пе( 1тег.сиггеиг )! ) Разве этот код не выглядит изящно? В одном операторе кода берется бесконечный список целых чисел и применяется деление, за которым следует операция возведения в квадрат. В итоге получается "лениво" вычисляемьгй тип 1ЕпивегаЬ1е<с)оиЬ1е>, который вычисляет каждый элемент только по мере необходимости. Функциональное программирование действительно очень удобно, если на него взглянуть с этой точки зрения. Разумеется, связывать в цепочки допускается произвольное количество операций. Например, в конец вычислений можно добавить операцию округления или, скажем, фильтрующую операцию, чтобы учесть только результаты, отвечающие некоторому критерию.
Для этого можно было бы предусмотреть обобщенный расширяющий метод ег1тег<т>, подобный тгапя тогв<т>, который принимает делегат предиката в качестве параметра и применяет его для фильтрации элементов коллекции. Наверняка вы задумались о действительно полезных расширяющих методах, которые можно создать для манипулирования данными. Не удивляйтесь, но целый на- Расширяющие методы 491 бор подобных методов уже существует. Загляните в класс Яувпет.
11пс(. ЕпцтегаЬ1е. Этот класс предоставляет полный набор расширяющих методов, которые обычно используются вместе с ЫМ(Ы, о чем речь пойдет в главе 16. ()гавное их отличие в том. что все зти расширяющие методы оперируют типами 1ЕпцппегаЬ1е<Т>. Кроме того, класс яузсеи.11пп(. ОцегуаЬ1е предлагает те же самые расширяющие методы для типов, реализующихинтерфейс 1ОцегуаЬ1е<Т>,который унаследован от 1ЕпшзегаЬ1е<Т>. Пользовательские итераторы В главе 9 рассматривались итераторы, которые были добавлены в С Ф 2.О. Там же были описаны некоторые способы создания пользовательских итераторов.
Расширяющие методы обеспечивают еще большую гибкость при создании итераторов для коллекций в очень выразительной манере. По умолчанию каждая коллекция, реализующая 1ЕпцпегаЬ1е или 1ЕпцшегаЬ1е<Т>, имеет итератор в прямом направлении, поэтому для перемещения по коллекции способом, отличным от принятого по умолчанию. понадобится пользовательский итератор. Кроме того, пользовательские итераторы нужно будет создавать для типов, не поддерживающих 1Епцте гаЬ1е<Т>, как будет показано в разделе "Заимствование из функционального программирования" далее в главе.















