Нэш Трей - C# 2010. Ускоренный курс для профессионалов (2010) (1160865), страница 138
Текст из файла (страница 138)
Лямбда-аыражения 515 (со11, сиг) => со11 [сиг[0]] [сиг[1] ], (сиг) > сиз[0] > 2 )( сиг[1] > 2, (сит) => пав апс[] [ сит[0) + 1, сит[1] + 1 ) ): Тогеась( чаг Тсев гп Тсег ) ( Сопзо1е.иг1Геьгпе( гсев ); ) Давайте посмотрим, насколько многократно используемым является Ма)геСизсов 1Гегат.от<>. Общеизвестно, что на привыкание к синтаксису лямбда-выражений уходит некоторое время, и те, кто имел дело в основном с императивным стилем нодирования.
могут столкнуться с определенными трудностями в его понимании. Обратите внимание, что МаяеСизсов1сегасог<> принимает три аргумента обобщенного типа. ТСо11есс) оп — это тип коллекции, который в рассматриваемом примере указан в точке использования как 11зс<11зс<боиЫе». ТСигзог — тип курсора, в данном случае являющийся простым массивом целых чисел, который может рассматриваться как координаты переменной вагггх. А т1гев — тип, возвращаемый кодом через оператор у1е1о.
Остальные аргументы типов махеспзгов1гегагог<> — зто типы делегатов, которые он использует для определения того, как выполнять итерацию по коллекции. Для начала нужен способ получения доступа к текущему элементу колленции, который в примере выражается следующим лямбда-выражением, использующим значения внутри массива курсора для индексации элементов внутри матрицы: (со11, сит) => сс11(сит[0) ) (сит[1) ) Затем необходим способ определения факта достижения конца ноллекции, для чего применяется следующее лямбда-выражение, которое просто проверяет, не вышел ли курсор за пределы матрицы: (спг) => сит[0) > 2 () сит[1) > 2 И, наконец, необходимо знать, как перемещать курсор, что задается следующим лямбда-выражением.
которое просто увеличивает обе координаты нурсора: (сит) => пеы гпг(] ( сит[0) з 1, сит[1] + 1 ) В результате выполнения кода примера должен появиться вывод вроде приведенного ниже, который показывает, что действительно происходит перемещение по диагонали матрицы от левого верхнего угла к нижнему правому. На каждом шаге по пути махесизгов1гегагог<> поручает работу, которую нужно выполнить, соответствующему делегату.
1 2.1 3.2 Другие реализации МахеСиз Гов1 Гегасог<> могут принимать первЫй парамЕтР типа 1Е пиве гаЬ1е<Т>. который в данном примере должен быть 1Еп иве гаЬ1е<с(оиЫе>. Однако в случаеустановкиэтогоограничениявсе,чтопередаетсямахесизгов1гегасот<>, должно реализовывать 1ЕпивегаЫе<>. Переменная васггх реализует 1ЕпивегаЬ1е<>, но не в той форме, которую легко использовать. поскольну это 1епивегаЬ1е<11зг<с(оиЬ1е». Вдобавок можно предположить. что коллекция реализует индексатор, как описано в разделе "Индексаторы" главы 4, но тогда это наложит ограничение на повторную применяемость макесиэгов1гегагог<> и то, какие объекты можно использовать в нем. В приведенном выше примере индексатор в действительности используется для об- 515 Глава! 5 ращения к конкретному элементу, но его применение вынесено наружу и упаковано в лямбда-выражение, предназначенное для доступа к текущему элементу.
Более того, поскольку операции доступа к текущему элементу коллекции вынесены наружу, можно даже трансформировать данные в исходной переменной аасгтх в процессе итерации по ней. Например, можно было бы умножить каждое значение на 2 в лямбда-выражении, которое обращается к текущему элементу коллекции; (со11, ссг) => со11(спг(0]](сит(1]] * 2; Только подумайте, насколько сложно было реализовать Ма)геСпягоп1гегаеог<> с применением делегатов во времена СВ 1.07 Именно это имеется в виду, когда говорится, что одно лишь появление синтаксиса лямбда-выражений в Са открывает разработчикам невероятные возможности. В качестве финального примера рассмотрим случай, при котором пользовательский итератор вообще не выполняет итерации по элементам, а вместо этого использует генератор чисел, как показано ниже: сятпч Еуягегю иятп9 Еуягет.ьтпя~ пя1п9 Еуясет.Со11есГ1опя.Еепегтс; рпЬ1>с с1аяя 1еегасогЕхатр1е яеагтс 1ЕппеегаЬ1е<Т> Ма)ге0епегасог<Т>( Т 1пте1а1Ча1пе, Еппс<Т, Т> апчапсе ) ( Т сиггепГЧа1пе = гптсга1Ча1иев еЬ11е( Сгсе ) ( уге10 гегпгп спггепеча1пев спггепГЧа1пе = аг(чапсе( спггепеча1пе )7 ясае1с чогб Магп() ( чаг гсег = Ма)ге0епегасог<оопЬ1е>( 1, х => х * 1.2 ); чаг еппеегасог = гсег.0еГЕппеегасог()7 ТОГ( 1ПС 1 = 07 Г < 10) ЕЕГ ) ( еппаегаеог.Моченехг()," Сопяо1е.кг1ге11пе( епсеегасог.спггепе ) Запустив этот код на выполнение.
можно увидеть следующий результат: 1 1.2 1.44 1.728 2.0736 2.48832 2.985984 3.5831808 4.29981696 5.159780352 Этот метод можно использовать для бесконечного выполнения,и тогда он остановится только по исключению переполнения памяти либо по принудительному завершению. Но главное то, что элементы. по которым выполняется итерация, не существуют Лямбда-выражения 517 Замыкание (захват переменной) и мемоизация В разделе "Остерегайтесь сюрпризов.
связанных с захваченными переменными" главы 10 было описано, как анонимные методы могут захватывать контекст своего лексического окружения. Многие называют это захаапюм переменной. На языке функционального программирования зто известно как замыкание (с1озиге)~. Нюке показан простой пример замыкания: пв1пч Яувсеи) пв1по Зувеев.й1ппг рп)>11с с1авв 01овпгев вевтас чоас Ма1п )) ( спг с)е1та = 1; рппс<1пг, 1пт> йппс = апг спггес1СЧа1 = О) Гог) апт 1 = 0; 1 < 10, спггепеуа1 = йппс) Сопво1е.игагеьапе( ) )х) => х е с)е1га; ( спггепгуа1 )) спггепеуа1 ); Переменная ае1еа и делегат гипс формируют замыкание.
Тело выражения ссылается на с)е1са и потому должно иметь доступ к ней при последующем выполнении. Чтобы обеспечить это, компилятор "захватывает" переменную для делегата. "За кулисами" это означает, что тело делегата содержит ссылку на действительную переменную с)е1са. Но обратите внимание. что с)е1га относится к типу значения и находится в стене. Компилятор должен предпринять какие-то действия, чтобы обеспечить существование переменной за пределами метода, в котором она объявлена, если есть вероятность, что делегат обратится к ней после выхода из этого контекста.
Более того, поскольку захваченная переменная доступна как делегату, так и контексту, содержащему лямбда-выражение, это означает, что захваченная переменная может быть изменена вне контекста и вне связи с делегатом. По сути, доступ к переменной имеют два метода — Маап и делегат. Такое поведение можно использовать в собственнык интересах, но если его не ожидать, то оно может привести к серьезной путанице. На заметку) В действительности при формировании замыкания компилятор С№ берет все зти переменные и упаковывает в сгенерированный класс. Он также реализует делегат как метод этого класса.
В очень редких случаях это придется учитывать, особенно если обнаружится влияние на производительность при профилировании. Теперь давайте рассмотрим полезное применение замыканий. Одна из основ функционального программирования состоит в том, что сама функция трактуется как первоклассный объект, которым можно манипулировать и оперировать, а также вызывать. Ранее уже было показано, как лямбда-выражения могут быть преобразованы в деревья выражений, чтобы можно было оперировать ими, производя более или менее сложные выражения.
Но один момент, о котором еще не упоминалось — это использование функ- Дополнительные сведения о замыканиях доступны по адресу Пг ер: г'г'гп. н1)с) ресНа. огсг' н№К№с Замыкание спрограммирование). в коллекции; вместо этого они генерируются по мере необходимости при каждом пе- ремещении итератора. Такую концепцию можно применять многими способами, даже реализуя генератор случайных чисел с помощью итераторов С№. 518 Глава 15 ций в качестве строительных блоков для создания новых функций.
Для быстрой иллю- страции того, что имеется в виду, рассмотрим два лямбда-выражения: х => х * 3 х => х з 3.1415 Создадим метод, комбинирующий эти лямбда-выражения для получения составного лямбда-выражения: из1пч Яузсев; из1пя Яузсев.Ьгпс1! риЬ11с с1зяя Совроипс) ( зеаехс типс<т, я> сьахп<т, В, я>( типс<т, В> типс1, тшгс<В, я> типс2 ) ( гесигп х => Типс2( Типс1(х) ); ) ясэс1с оогс) Мзгп() ( рипа<!по, яоиь1е> гипс = сьагп( (1пг х) => х * 3, (1пг х) => х с 3. 1415 ); Сопяо1е.яг1сеагпе( гипс(2) )! ) ) Метод СЬа10<> принимает два делегата и производит третий делегат, комбинируя первые два.










