Нэш Трей - C# 2010. Ускоренный курс для профессионалов (2010) (1160865), страница 135
Текст из файла (страница 135)
Обратите внимание, что лямбда-выражеиие лишено информации о типе. Это ие значит, что выражение ие имеет типа. Вместо этого компилятор выводит тип аргумента и тип результата из контекста его использования. Отсюда следует, что если лямбда-вырэжеиие присваивается делегату, то типы определения делегата используются для определения типов внутри лямбда-выражеиия. Ниже показан код, в котором лямбда-выражеиие присваивается типу делегата: оя1по Яуяоеев оя1пч Яуягез.Ь1пои РоЬ11о с1зяя Ьаепоатеяс ( яеаг1о тото Ияьп(! ( Ропо<1по, с(ооЬ1е> ехрг = х => х l 2; >по яоее((озЬег = 9; Сопяо1е.иг1яеьапе( "Результат: (О!", ехрг(яоеековЬег( Некоторые примеры фуиющоизлького программирования с использованием анонимных методов приведены в главе 14.
))ямбдз-выражения 505 Лямбда-выражение выделено полужирным. Рцпс<> — это вспомогательный тип, предоставленный в пространстве имен $уясев, который можно использовать для объявления простых делегатов, принимающих до четырех аргументов и возвращающих результат. В данном случае объявляется переменная ехрг, которая является делегатом, принимающим 1пС и возвращающим с(оцЬ1е. Когда компилятор присваивает лямбдавыражение переменной ехрг, он использует информацию о типе делегата для определения того, что типом х должен быть гпс, а типом возврата — боцЬ1е. Если запустить этот код на выполнение, можно заметить, что результат не совсем точен — было произведено округление.
Этого следовало ожидать, поскольку результат х/2 представлен как 1пС, который затем приводится к боцЬ1е. Для исправления ситуации необходимо соответствующим образом указать типы в объявлении делегата: цяьпд зуяСевг саяпин зуягев.11пси рцЬ11с с1аяя ьавпбатеяс ( ягаг)с чогб Иа1п() ( Ропе<с(оиЬ1е, боцЫе> ехрг = (с(опЬ1е к) => х у 2; гпс яовеицвЬег = 9г Сопзо1е.нг1Сеьяпе( "Результат: (0)", ехрг(яовеицвЬег) Для целей демонстрации в лямбда-выражение включено то, что называется явно типизированным списком параметров, и в этом случае х имеет тип с)оцЬ1е. Также обратите внимание, что типом ехрг теперь является Ропе<с)оцЬ1е, боцЬ1е>, а не Рцпс<ьпг, боцЬ1е>. Компилятор требует, чтобы при использовании типизированного списка параметров в лямбда-выражении и присваивании его делегату типы аргументов делегата в точности совпадали с этим списком.
Однако поскольку 1пС явно преобразуем в боцЬ1е, допускается передать яовеицвЬег в ехрг во время вызова, как было показано. На заметку! Следует отметить, что типизированный список параметров должен быть заключен в скобки. Скобки также требуются при объявлении делегата, принимающего ипи больше одного параметра, ипи вообще не принимающего параметров. На самом деле скобки можно использовать всегда; они не обязательны з пямбда-аыражениях с только одним типизированным параметром.
Когда лямбда-выраженне присваивается делегату, тип возврата выражения обычно выводится из типов аргументов. Поэтому в следующем фрагменте кода типом возврата выражения будет боцЬ1е, поскольку предполагаемьгй тип параметра х — боцЬ1е: Рцпс<боцЬ1е, 1пе> ехрг = (х) => х у 2; уу Ошибка компиляции!!! Но так как тип с)оцЬ1е не может быть неявно преобразован в Тпс, компилятор выдает ошибку: еггог С$1662: Саппос сопчегс '1авЬба ехргеяя1оп' Со бе1едасе Суре 'зуясев.рцпс<боцЬ1е,1пс>' Ьесацяе зове об СЬе гесцгп курев гв Спе Ыосх аге поС Твр11с1С1у сопчегСТЫе Со Спе бе1ечасе геСцгп Суре ошибка С$1662г Не удается преобразовать ллмбда-вырахение в тип делегата буяСев.ропс<доиЫе,упС>, т.к.
некоторые из типов возврата в блоке не лвляптсл нелвно преобразуемыми к возвращаемому типу делегата Исправить это можно, добавив приведение результата лямбда-выражения к 1пг: Рцпс<соцЬ1е, 1пе> ехрг = (х) => (хпе) х У 2; 506 Глава!5 Нв заметку! Явные типы в списке параметров лямбда-вырвжения необходимы, если делегат, которому оно присваивается, имеет параметры опт или ге Р, Кто-то может сказать, что явная фиксация типов параметров внутри лямбда-выражений лишает его элегантности и выразительной мощи. И это определенно затрудняет чтение кода.
Теперь рассмотрим простое лямбда-выражение, не принимающее параметров: ия1пя Яуятев; ия1пд Буятев.11пЧ) риЬ11с с1аяя ЬапьоаТеят ( ятаттс чосб Ма1п() ( тпт соипгег = 0; Нггтеятгеап( () > сооптег++ )) Сопяо1е.хтэтеЬтпе( "Финальное значение счетчика: (О)", соиптег ); ятагтс чотб хт1теэттеав( Ропс<1пт> депетатот ) ( Тот( 1пт т = Ог т < 10; ++1 ) ( сопяо1е.хгтге( "(О), ", яепетагот() )) ) Сопяо1е.
Хг1тет Ьпе () ) Обратите внимание, насколько просто с помощью лямбда-выражения передать функцию в качестве параметра в метод Хт1теэттеав. Более того, переданная функция захватывает окружение, внутри которого она выполняется, а именно — значение сооптет в ма1п. Зга захваченное окружение и функция вместе обычно называется замыканием (с)озцге).
И. наконец, ниже приведен пример лямбда-выражения, принимающего более одного параметра: 1пч Буятев; ия)пс Буятеп.11пчг пятпс Яуятев.со11ест1опя.оепет1сг роЬ11с с1аяя Ьавьпатеят ( ятаттс чо1О Матп() ( чат теавмевЬегя = лен тэят<ягттпд> ( "1оо ?.ооп1я", "эпохе Рогтегноояе", "раппу Моопап", "Ту ХеЬЬ" Р1пявуР1гятнаве( теавиепЬегя, "раппу", (к, у) и> к.Сопкаэпа(Р) ) ) ятаттс чотб РьпОВугэгятиапе( 11ят<яггтпс> пепЪегя, ятт1пд 11гятиаве, Рипс<ятт1пд, ятг1пя, ьоо1> ртео1сате ) ( Гогеасп( чат вевЬег тп пепЬегя ) ( 11( ргестсате(певЬег, 11гятхаве) ) ( Сопяо1е.иг1те11пе( певЬег ); ) ) ) Лямбда-аыражения 507 В данном случае лямбда-выражение используется для создания делегата, принимающего два параметра типа ясг1сд и возвращающего значение Ьоо1. Кзк видите, лямбда-выражение предлагает симпатичный и краткий способ создания прсдикатов.
В разделе "Вернемся к итсраторам и генераторам" далее в главе будет показана новая версия примера из главы 14, которая демонстрирует использование лямбда-выражсний в качестве прсдикатов для создания гибких нтераторов. Замыкания в СФ 1.0 В "старые добрые времена" С№ 1.0 создание замыканий было болезненным процессом и приходилось поступать приблизительно так: сятяд Яуягеа; спяеяе рсЬ11с с1аяз МуС1ояоге ( рсЬ11с Мус1оячге( ьаяь соиаевг ) ( ЬЬ1я.соиасег = сосасег; ) рсЬ11с се1едеев ьая 1асве1едасв() ( ряЬ11с 1асве1едаге ПеФЛ)е1вдаге() ( геесга пех 1асве1едаге( 1асгевеперсасе1оа ); ) рг1чаее аае Тпсгввеасрсасе1оа() ( геесга (ьсосагег)++( ) рг1чаев 1се" сосаяег; ) рсЬ1ьс с1аяя Ьятссятеяс ( исяяГе ясястс кс1г( Мя(с() ( 1пс соиссег = 0; мус1озаге с1озсге = авн мус1озаге( асоипсвг ); Хггсеэсгеап ( с1оясге.
Яессе1едясе О ) ~ Сосяс1е.иг1се11се( "Финальное значение счетчика: (О)", сссссег ) ) ягагтс котс хгтгеяггеав( мус1ояаге.гвспв1едасв 1асгевеасог ) ( Гсг( 1сс 1 = 0~ 1 < 10; ++1 ) ( ССЬЯО1Е.ХГТГЕ( "(О), ", ТПСГЕтЕСГСГ() ); ) Сссяо1е.Хг(сеьасе(); ) ) Посмотрите, какой объем работы потребовалось выполнить, чтобы обойтись без лямбда-выражсний.
Дополнительный код и прочие изменения выделены полужирным. Первая задача состоит в создании объекта для представления делегата и его окружения. В данном случае окружение — это указатель на переменную соспсег в методе Мямь Для инкапсуляции функции и сс окружения было решено испольэовать класс. Обратите внимание, что для этого в классе МуС1яяя применяется небезопасный код. Небезопасный код необходим при использовании указателей в С№. потому что безопасность этого кода нс может быть проверена С(.йФ.
Затем в методе Маус я создал экземпляр МуС1оясге и передал делегат, созданный вызовом Яеспе1едасе методу хгусеяггеааь Тема написания небезопасного кода на С№ выходит за рамки настоюцей книги. За дополни- тельными деталями по этому поводу обращайтесь в документацию МЯ()М. 508 Глава (5 Какой объем работы! К тому же и понять такой код совсем нелегко.
На заметку! Возможно, возник вопрос, почему в предыдущем длинном примере применялся указатель, для чего компиляция должна была выполняться с включенной опцией г цпяабе. Причина в том, что нужно было подчеркнуть, что захваченная переменная может быть изменена вне кода, использующего ее. Когда компилятор С» встречает переменную в замыкании, он делает нечто подобное, но вместо использования указателя на встреченную переменную он инициализирует общедоступное поле сгенерированного класса, реализующего замыкание, ссылкой на захваченную переменную или ее копией, если она имеет тип значения. Однако любой код, который попытается модифицировать эту переменную вне контекста замыкания, модифицирует копию внутри объекта замыкания, поскольку, в конце концов, это общедоступное поле.














