Нэш Трей - C# 2010. Ускоренный курс для профессионалов (2010) (1160865), страница 79
Текст из файла (страница 79)
И, наконец, нужно создать делегаты, привязать их к этим методам и зарегистрировать в процессоре. Зги действия выглядят не связанными в своем потоке. Может показаться, что более естественно определять методы делегатов каким-то менее многословным способом. Часто случается так, что инфраструктура, необходимая для применения делегатов, затрудняет понимание кода, поскольку фрагменты механизма разбросаны в разных местах кода. Анонимные методы обеспечивают более простой и компактный способ определения простых делегатов, подобных этим.
Если выражаться кратко, то анонимные методы (появившиеся в .(т(ЕТ 2.0) позволяют определять тело метода делегата в точке создания его экземпляра. Давайте посмотрим, как можно модифицировать предыдущий пример для использования анонимных методов. Ниже показана измененная часть примера: рцЬ11с с1авв ЕпсгуРотпс ( рггуасе всасус чо1т) Рггпсаггау( 1пс(] аггау ) ( Рог( апс 1 О; 1 < актау.Ьепосн| 4+к ) ( Сопво1е.итаке( "(0)", актау[1) РГ( 1 != актау.ьепос)т-1 ) [ Сопзо1е.нг1се( ", " ): ) ) Сопво1е.итаке( "1п" ); ) всасас чогб Маап() ( О Создать массив целых чисел.
1пс[) упседегв = пеи 1пс[) ( 1, 2, 3, 4 )' Ргосеввог ргос = пен Ргосеввог()т ргос. Ясгасесу = с(е1епасе(ьпс х) ( геецгп хв2т )т РгапГАггау( ргос.Ргосевз(1пкедегв) ргос.зсгасесу = бе1едасе(апе х) ( гегигп хает ): Ргтпсхггау( ргос.ргосезз(1псеоегв) ргос.зсгасесу = к(е1едаее ( геецгп От )( Рггпсхггау( ргос.ргосевв(1псесегв) 298 Глаза!0 Обратите внимание, что два метода — Мп111р1уВу2 и Мп1сар1уВул — исчезли. Вместо этого делегат создается с применением специального синтаксиса анонимных методов в точке, где он присваивается свойству Ргосеввог.
ясгзседу. Как видите, этот выглядит синтаксис почти так же, как если бы объявление делегата и метод, привязываемый к делегату, смешались в одно целое. В принципе, в любом месте, где разрешено передавать экземпляр делегата в качестве параметра, вместо него можно передать анонимный метод. При передаче анонимного метода в списке параметров, принимающем делегат, или во время присваивания анонимного метода типу делегата следует учитывать преобразование типа анонимного метода, "За кулисами" анонимный метод превращается в обычный делегат. который трактуется как любой другой экземпляр делегата. Присваивая анонимный метод экземпляру делегата, следует соблюдать несколько правил.
° Во-первых. типы параметров делегата должны быть совместимыми с типами параметров анонимного метода. При первых двух использованиях делегатов в предыдущем примере был показан длинный путь объявления анонимного метода. Некоторые из вас. возможно, заметили отличие синтаксиса третьего использования делегата в этом примере. Был опущен список параметров, поскольку в теле метода они даже не используются. Тем не менее. все равно остается возможность установить свойство Ясгаседу по этому анонимному методу, потому ясно, что некоторое преобразование типов здесь все-таки происходит.
Вообще говоря, если анонимный метод не имеет списка параметров, то он является преобразуемым к типу делегата со списком параметров до тех пор, пока этот список не включает параметры опь и геГ. Если параметры опс присутствуют. то анонимный метод обязан перечислить их в списке параметров в точке объявления. ° Во-вторых, если анонимный метод перечисляет какие-то параметры в своем объявлении, то их количество должно совпадать с параметрами типа делегата, а типы параметров должны быть теми же самыми, что и типы в объявлении делегата.
° В-третьих, тип возврата анонимного метода должен быть неявно преобразуемым к объявленному типу возврата делегата. Поскольку синтаксис объявления анонимного метода явно не устанавливает тип возврата, компилятор должен проверить каждый оператор гегпгп в анонимном методе и убедиться, что он возвращает тип, отвечающий правилам преобразования.
Захваченные переменные и замыкания До сих пор анонимные методы позволили сэкономить некоторый объем клавиатурного набора и сделали код более читабельным. Но давайте рассмотрим правила областей видимости, связанные с анонимными методами. Вы уже знаете, что в СЗ фигурные скобки определяют единицы вложенных областей. Скобки, отделяющие анонимные методы, не являются исключением. Взглянем на слелуюшую модификацию предыдущего примера: пвапч Яувгепл ривуас яе1еязсе гпс Ргосяггагеду( 1пс х РиЬ11с с1авв Ргосеввог 1 рггхзсе Ргосзсгасеяу всгасеяул рпЬ11с Ргосзсгасечу Ясгаседу 1 вес 1 всгасечу = гз1пе; ) Делегаты, анонимные функции и события 299 риЬ11с 1пт[] Ргосезз( 1пт[] аггау ) 1пт[) тези1т = пен 1пт[ актау.ьепдть тот( тпт 1 = 0) 1 < аттау.ьепдгьк ++1 ) ( гези1т[1] = зтгатеду( аггау[1] ); ) тетигп тези1т) ) ) риЬ11с с1авв Растит ( риЬ11с Растог( 1пт таст ) ( ГЬ1в.таст = таст; ) ртачате 1пт Растк риЬ11с РтосБГтатеду Ми1тар11ег ( дет ( // Это анонимный метод.
гетитп бе1едате(1пт х) ( гетитп х*таст) )' ) ) риь1тс РгосБгтатеду А<)сег ( дет [ // Это анонимный метод. гетитп г(е1едате (1пт х) ( гетигп в+Расс) ) риЬ11с с1азз Ептгуро1пт ( ртгчате втат1с чо1и РггптАтгау( тпт[] актау ) ( Рог( 1пт 1 О) 1 < актау.1епдтьг ++1 ) ( сопзо1е.нг).ге( "(О]", актау[).] )) Н ( 1 != актау.1епдГЬ-1 ) ( Сопво1е.ыг1те( ", " ); ) ) Сопво1е.ыг1те( "1п" ); вгатас чо1г) Ма1п() ( // Создать массив целых чисел. 1пт[] гптедетз = пен апт[] ( 1, 2, 3, 4 )) Растог тастог пен Растот( 2 )г Ргосеввог ртос = пен Ргосеввог(); ргос.атгатеду = Еастог.ми1тар11ег; Рттптдтгау( ргос.ргосевв(тптедетз) ) ргос.ЯГгатеду Растит.дкн(етг Растит = пи11) РтаптАтгау( ргос.Ртосевз Оаптедетв) ) 300 Глава 10 В приведенном примере обратите особое внимание на класс Рассея, представляющий коэффициент.
Код в этом примере был сделан более гибким, чтобы можно было применять коэффициент по-разному, используя либо умножение, либо сложение. Как видите, анонимные методы в нлассе Ряс гог используют переменную, доступную в области, где она объявлена, а именно — поле экземпляра гасе. Это можно сделать потому, что обычные правила областей видимости действуют и в отношении блока анонимного метода. Однако здесь есть некоторые тонкости.
Видите, где переменная экземпляра гяссог в Иа1п устанавливается в пи11? Установка сделана перед обращением к делегату, полученному из свойства Рясгог.))сЫег. Это нормально, потому что свойство АсЫег возвращает экземпляр делегата, даже несмотря на то, что делегат объявлен как анонимный метод. а не обычным образом.
Но как насчет поля экземпляра Расгог. 1асг? Если переменная гяссог устанавливается в по11 внутри Матп, то сборщик мусора (ОС) может подобрать объект 1ассог даже перед самим делегатом. использующим поле, и с ним будет покончено, правильно? Может ли это привести к возникновению условия состязаний, если ОС подберет экземпляр Рясгог. 1асг перед тем, как делегат закончит работу с ним? Ответ отрицательный, потому что делегат захватил переменную. При объявлении анонимных методов любые переменные, объявленные вне контекста анонимного метода, но доступные внутри него, включая ссылку ЬЬ 1 я, рассматриваются как внешние переменные. И если тело анонимного метода ссылается на эти переменные, то говорят, что анонимный метод "захватил" (сар1пге) переменную. В данном случае захвачено ЬЬ1я.
Поэтому поле Расеог. 1асс в предыдущем примере продолжает существовать, поскольку на него ссылаются активные делегаты. Возможность обращения из тел анонимных методов к переменным, принадлежащим к контексту, в котором зти методы были определены, чрезвычайно удобна. В программировании это имеет общее название замыкания (с]озпге). Только представьте, насколько сложнее было бы реализовать тот же самый механизм, что приведен в примере, с использованием обычных делегатов. Пришлось бы создавать механизм, внешний по отношению к делегату, который бы поддерживал коэффициент, планируемый для использования в делегате.
Одним иэ возможных решений при использовании стандартных делегатов может быть ввод дополнительного промежуточного уровня в форме класса, кан это часто делается при решении подобных проблем. Однако нельзя не согласиться с тем, что анонимные методы позволяют сэкономить массу работы, не говоря уже о том, что они делают код более кратким и читабельным.
Остерегайтесь сюрпризов, связанных с захваченными переменными Когда переменная захвачена экземпляром анонимного метода, следует соблюдать осторожность в отношении возмоясных последствий. Имейте в виду, что представление захваченной переменной находится где-то в куче, а переменная в экземпляре делегата — зто просто ссылка на эти данные. Поэтому вполне возможно, что два экземпляра делегатов, созданных из анонимных методов, могут содержать ссылку на одну и ту же переменную. Ниже показан пример кода, иллюстрирующий сказанное. паапа Буясевс рпЬ11с Ве1едяге яо1с) Рггпслпп1псгевепг () 1 риЬ11с с1яяя НпггуРогпг рпЬ11с ягагтс Рггпглпп1псгевепг(] Сгеясеое1езясея() ( Рггпглпз1псгевепг(] с(е1еоясея = пен Рг1пслпс(1псгевепг(3]с 1пя эовечагьзЬ1е в 01 1пс япоГЬяг?ягааЬ1е = 11 Делегаты, анонимные функции и события 301 Рог( 1пС 1 = Ог г < 3; эе1 ) ( бе1еозгея[1] = бе1еоаСе ( Сопяо1е.ыгаееьепе( яопеуаггаЬ1еэв ); )1 геспгп бе1едасеяг ) ясасас тоьб ма1п О Ргтпглпб1псгепепг[] бе1еоасея = Сгеагеое1еоагея() сог( гпС г = О; 1 < 3) эе1 ) ( бе1еоагея[1]()к ) ) ) Анонимные методы внутри метода Сгезгепе1еозгея захватывают яоглетгаг1аЬ1е— локальную переменную в контексте метода сгеасеРе1еоасея.
Однако поскольку в массив помещены три экземпляра анонимных методов, получается, что эти три экземпляра захватывают один и тот же экземпляр переменной. Поэтому запуск приведенного выше кода на выполнение дает следующий результат: При вызове каждый делегат печатает и инкрементирует одну и ту же переменную.















