Г. Шилдт - С# 3.0 Полное руководство. 2010 (1160798), страница 89
Текст из файла (страница 89)
вкг = вкгор("Зто простой тест."); Сопво1е.иггсетвпе("Результирующая строка: " + всг); Сопво1е.иг1кеь1пе()т астор = во.иещочезрасевт всг = всгор("Зто простой тест."); Сопво1е.иг1се11пе("Результирующая строка: " т ввг)т Сопво1е.иг1веъ1пе()т всгор = во.вечегвет всг = эктор("Зто простой тест."); Сопво1е .Иггкег Рпе ("Результирующая строка: " + ввг) т ) Результат выполнения этого кода получается таким же, как и в предыдущем примере, ио иа этот раз делегат обращается к методам по ссылке иа экземпляр объекта класса Ббг1ппбрв. Групповая адресация Одним из самых примечательных свойств делегата является поддержка групповой адресации. Попросту говоря, групповая адресация — это возможность создать список, или цепочку вызовов, для методов, которые вызываются автоматически при обращении к делегату Создать такую цепочку нетрудно. Для этого достаточно получить экземпляр делегата, а затем добавить методы в цепочку с помощью оператора т или +=.
Для удаления метода из цепочки служит оператор — или -=. Если делегат возвращает значение, то им становится значение, возвращаемое последним методом в списке вызовов. Поэтому делегат, в котором используется групповая адресация, обычно имеет возвращаемый тип чо1д. Ниже приведен пример групповой адресации. Это переработанный вариант предыдущих примеров, в котором тип значений, возвращаемых методами манипулирования строками, изменен иа чогб, а для возврата измененной строки в вызывающую часть кода служит параметр типа ген. Благодаря этому методы оказываются более приспособленными для групповой адресации. // Продемонстрировать групповую адресацию.
цв1пс Бувкещт // Объявить тип делегата. бе1епаке чо1б Бкгиоб(гег ввг1пп вкг) с1авв Мц1К1савсвеюо ( // Заменить пробелы дефисами. Глава )б. Делегаты, события и лямбда.выражения 463 яласас та1с( Яер1асеБрасея(тел ятг1пе я) ( Сопяо1е.иг1тебапе("Замена пробелов дефисами."); в = я.аер1асе(' ', ' †')т // Удалить пробелы. втав1с то10 КевотеБрасея(тел ятглпе в) ( ясг1пд севр = ""; 1пг 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асеарт ятгбр += гевотеарт втг = "Зто простой тест."т // восстановить исходную строку // Обратиться к делегату с групповой адресацией. ввгбр(геа ввг)т 464 Часть ).
Язык О(( сопзо1е.игггепвпе (" Результирующая строка: " + згг); Сопзо1е.нггсеьгпе Ц: ) ) Выполнение этого кода приводит к следующему результату: Замена пробелов дефисами. Обращение строки. Результирующая строка: .тсет-йотсорп-отэ Обращение строки. Удаление пробелов. Результирующая строка: .тсетйотсорпотз В методе )(а1п () иэ рассматриваемого здесь примера кода создаются четыре экземпляра делегата. Первый из них, зсгбр, является пустым, а три остальных ссылаются на конкретные методы видоизменения строки.
Затем организуется групповая адресация для вызова методов Нещочезрасез () и Нечегзе () . Это делается в приведенных ниже строках кода. зсгор = гер1асезру зсгбр Ь= гечегзеЯГг Сначала делегату зсгбр присваивается ссылка гер1асеБр, а затем с помощью оператора ь= добавляется ссылка гечегзезгг. при обращении к делегату зггбр вызываются оба метода, заменяя пробелы дефисами и обращая строку, как и показывает приведенный выше результат.
Далее ссылка гер1асеэр удаляется из цепочки вызовов в следующей строке кода: зсгбр -= гер1асезр) и добавляется ссылка гещочеЯр в строке кода зсгбр += гещочезр; После этого вновь происходиъ обращение к делегату з егор. На этот раз обращается строка с удаленными пробелами. Цепочки вызовов являются весьма эффективным механизмом, поскольку они позволяют определить ряд методов, выполняемых единым блоком. Благодаря этому улучшается структура некоторых видов кода. Кроме того, цепочки вызовов имеют особое значение для событий, как станет ясно позже.
Ковариантность и контравариантность Делегаты становятся еще более гибкими средствами программирования благодаря двум свойствам: ховариантности и контравариантности. Как правило, метод, передаваемый делегату, должен иметь такой же возвращаемый тип и сигнатуру, как и делегат. Но в отношении производных типов это правило оказывается не таким строгим благодаря ковариантности и контравариантности. В частности, ковариантность позволяет присвоить делегату метод, возвращаемым типом которого служит класс, производный от класса, указываемого в возвращаемом типе делегата.
А контравариантность позволяет присвоить делегату метод, типом параметра которого служит класс, являющийся базовым для класса, указываемого в объявлении делегата. Ниже приведен пример, демонстрирующий ковариантность и контравариантность. Глава )5. Делегаты, события и ляыбда-выражения 465 // Продемонстрировать ковариантность и // контравариантность. пз1пд Яузсещ) с1азз Х ( рпбагс гпл Ча1т ) // Класс У, производный от класса Х. с1азз У: Х ( ) // Этот делегат возвращает объект класса Х и // принимает объект класса У в качестве параметра. бе1едасе Х Спапде1С(У оЬ))) с1азз СоСоплгауагаапсе ( // этот метод возвращает объект класса х и // принимает объект класса Х в качестве параметра.
зпалгс Х 1псгИ(Х оЬ)) ( Х Сещр = пен Х()1 гещр.ча1 = оь).ча1 ь 1т геспгп сеюрт // Этот метод возвращает объект класса У и // принимает объект класса у в качестве параметра. зсасгс у 1псгВ(у опав) ( х сеюр = пен у() т сещр.ча1 = оьт.ча1 + 1т гесигп сещрт зсаггс нога Магп() ( у уоЬ = пен у()т // В данном случае параметром метода 1псги является // объект класса Х, а параметром делегата Спапце1С— // объект класса у. Но благодаря контравариантности // следующая строка кода вполне допустима.
СЬапде1С опалые = 1псгдт Х ХоЬ = сваппе(уоЬ)т сопзо1е.иг1се11пе("хоь: " + хоь.ча1)т // В этом случае возвращаемым типом метода 1псгВ // служит объект класса у, а возвращаеыым типом // делегата СЬапде1С вЂ” объект класса Х. Но благодаря // ковариантности следующая строка кода оказывается // вполне допустимой. пиалке = тпсгВ) уоЬ = (у) спапое(уоЬ)т 466 Часть ).
Язык С» Сопао1е.ксгкеъгпе(ттоЬ. "" + Топ.тта1)т ) Вот к какому результату приводит выполнение этого кода: Хоь: 1 тоь: 1 В данном примере класс 1 является производным от класса Х. А делегат сьапде1С объявляется следующим образом: т)е1едасе х сьапде1с (т оЬ)) т Делегат возвращает объект класса х и принимает в качестве парамет ь, объект класса 1. А методы 1псгА () и 1псгВ () объявляются следующим образом: эСаС»с Х 1псгА(Х оЬЗ) агакгс Х 1псгв(т ЬЬ») Метод 1псгА() принимает объект класса х в качестве параметра и возвращает объект того же класса.
А метод 1псгВ () принимает в качестве параметра объект класса х и возвращает объект того же класса. Но благодаря ковариаитиости и коитравариаитиости любой из этих методов может быть передан делегату сьапде1с, что и демонстрирует рассматриваемый здесь пример. Таким образом, в строке Спапде1С спапде = 1пссАт метод 1псгА() может быть передан делегату благодаря коитравариаитиости, так как объект класса х служит в качестве параметра метода 1псгА(), а объект класса 1 — в качестве параметра делегата сьапде1с. Но метод и делегат оказываются совместимыми в силу коитравариаитиости, поскольку типом параметра метода, передаваемого делегату служит класс, являющийся базовым для класса, указываемого в качестве типа параметра делегата.
Приведенная ниже строка кода также является вполне допустимой, ио иа этот раз благодаря ковариаитиости. спапде = 1пагВт В данном случае возвращаемым типом для метода 1псгВ () служит класс Х, а для делегата — класс Х. Но поскольку возвращаемый тип метода является производным классом от возвращаемого типа делегата, то оба оказываются совместимыми в силу ковариаитиости. Класс Зув Веха. эе1е»та1е Все делегаты и классы оказываются производными неявным образом от класса Вуэгев. Ре1едаге.
Как правило, членами этого класса ие пользуются непосредственно, и это ие делается явным образом в данной книге. но члены класса Вуэсеат. Ве1едасе могут оказаться полезными в ряде особых случаев. Назначение делегатов В предыдущих примерах был наглядно продемонстрирован внутренний механизм действия делегатов, ио зти примеры ие показывают их истинное назначение. Как правило, делегаты применяются по двум причинам. Во-первых, как упоминалось ранее в этой Глава 15. Делегаты, события и лямбда.выражения 467 главе, делегаты поддерживают события. И во-вторых, делегаты позволяют вызывать методы во время выполнения программы, не зная о них ничего определенного в ходе компиляции.
Это очень удобно для создания базовой конструкции, допускающей подключение отдельных программных компонентов. Рассмотрим в качестве примера графическую программу, аналогичную стандартной сервисной программе тт(г(пс(оитз Ра(пС. С помощью делегата можно предоставить пользователю возможность подключать специальные цветные фильтры или анализаторы изображений. Кроме того, пользователь может составлять из этих фильтров или анализаторов целые последовательности. Подобные возможности программы нетрудно обеспечить, используя делегаты.