Нэш Трей - C# 2010. Ускоренный курс для профессионалов (2010) (1160865), страница 89
Текст из файла (страница 89)
пазла Буясев; цязпд Яуягев.со11ес11опя.оепег1ся с1авв А ( ) с1азз В: А ( 1псеггасе 1МуСо11есгзоп<Т> ( чоуц Аг)о1сев( Т Тсев ); ) 1пеегтасе 1мутгззвмь1есо11есезоп<1п т> ( чом1 КевочеХЕев( Т 1еев ); ) с1аяя мусо11ессгоп<т>: 1мусо11есгзоп<т>, 1мутг1вваь1есо11есс1оп<т> ( рпЬ11с чо14 Ацг)1гев( Т 1сев ) ( со11есгзоп.лоб( Тгев ); рсЬ11с чозс Еевоче1сев( Т Тсев ) ( со11есгзоп.невоче( Тсев ); ) ргзчяге 11яс<Т> со11есгзоп = пен 11яс<Т>(); ясасус с)аяя ЕпггуРо1пс ( 338 Глава 11 зтат1с чотб Ма1п() ( чат 1сепз = печ мусо11есс1оп<А> О ~ 1тепз.Асс(1тев( печ А() )/ В Ь = печ В(); Ттепз.АОЕ1теп( Ь )) 1нуТттлилавтеСо11есттоп<А> со111Сепз Ттевз) // Коперапариакпаоепь ° дейопппп! ТиуттсавжЬ1аСО11ЕОС1ОП<В> СтсПСО11 и ОО111СЕПЗ) Стапсо11.
Клжоча1еап( Ь ) ' Чтобы можно было сосредоточиться исключительно на случае с контравариантностью, некоторый код из примера ковариантности удален. Обратите внимание на использование ключевого слова тп в объявлении интерфейса 1мутт1ппаЬ1есо11есс1оп<т>. Это говорит компилятору, что в соответствии с необходимой операцией (усечение в данном примере) существует неявное контравариантное преобразование из 1пуТттлчлабуеСо11естаоп<А> в 1пуТтпшлаЬ1еСо11есС1оп<В>,поскольку имеется неявное контравариантное преобразование из В в А. На первый взгляд преобразование и присваивание со111сепз переменной ст1пСо11 может показаться непонятным. Но если подумать, что если для МуСо11есС1оп<А> можно вызвать Вепоче1Сеп, передав экземпляр А, значит, на основе правил наследования должен существовать возможность вызова Вепоче1сеп с передачей ему экземпляра В.
До настоящего момента примеры ковариантности и контравариантности были показаны с использованием модификащщ одного и того же выдуманного класса коллекций. При этом перечисление на коллекции было ковариантным, а удаление из коллеющи— контравариантным. Но как насчет добавления элементов в коллекцию? Какую разновидность вариантности представляет оно? Уже доступен интерфейс 1мусо11есстоп<т>, который для удобства повторяется ниже; Тптеттасе 1МуСо11естаоп<Т> ( чотп Аоо1Сеп( Т ттеп )) ) Поскольку имеется ссылка на 1мусо11есстоп<А>.
значит, должна существовать возможность добавления акземпляров В, так как В наследуется от А. Поэтому вызов Абб1сеп на 1мусо11ессаоп<А> с передачей экземпляра В должен быть аквивалентным вызову на 1мусо11есс).оп<А> с передачей экземпляра В. Таким образом, операция добавления экземпляра в коллекцию контравариантна по определению. То есть, если тип В преобразуем в А и 1мусо11ессаоп<В> преобразуем в 1мусо11есстоп<А>, то операция конт равари анти а. Теперь, определив, что операция добавления элемента в коллекцию является контравариантной, следует декорировать интерфейс так, как показано ниже: тптеттасе 1МуСо11есстоп<1п Т> ( чотб Ас(О1теп( Т Ттеп ); Инвариантность Обобщенный тип интерфейса или делегата, где параметры обобщения не декорированы вовсе, является инвариантным.
Естественно, все такие интерфейсы и делегаты Обобщения 339 были инвариантными и до появления С№ 4.0, потому что не существовало декораций я и и ооо для параметров обобщений. Как было показано в предыдущем разделе, наш надуманный интерфейс 1мусо11еосьоп<т> выглядит так: Епоеггаое 1МуСо11еооьоп<Т> ( товб Абб1ЕЕп( Т Есеп )) Т Яео1геп( Епк Епбех ); ) Если эти два метода должны быть сохранены в одном и том же интерфейсе, значит, нет иного выбора. кроме как оставить этот интерфейс инвариантным. Если компилятор позволил бы декорировать параметр обобщения Т ключевым словом ооо. то произошла бы та же ситуация, что и с ковариантностью массивов.
То есть, можно было бы компилировать код, в котором разрешено добавлять в коллекцию несовместимые типы. Почему таку Представим на мгновение, что показанный выше интерфейс мог быть помечен следующим образом: // Это не будет работать! Епееггаое 1мусо11есоьоп<оос т> ( оотб Абб1сеп( Т Есеп ) ) Т Оес1сеп( Епо Епбех )) ) Затем, основываясь на определении ковариантности, переменную типа 1МуСо11ес сьоп<зсгьпд> можно было бы присваивать переменной типа 1мусо11ессьоп<оьб есс>. И тогда через последнюю переменную удалось бы сделать нечто подобное показанному ниже: // Ничего кроме вреда! Мусо11ессьоп<зсг1по> зсг1пдв = ...; 1мусо11еооеоп<оь1есс> оьбесез = зсггпдвг оЬбессз.Абб1оеп( пен Мопкеунгепсп() Таким образом, большая часть головной боли, связанной с инвариантностью массивов в С№. набегается за счет использования обобщений, которые объединены с синтаксисом, добавленным в С№ 4.0.
Другими словами, правила вариантности для обобщений обеспечивают безопасности типов, в то время как правила вариантности для простых старых массивов — нет. Вариантность и делегаты Обобщенные делегаты следуют тем же правилам, что и обобщенные интерфейсы, применяя декорации вариантности к параметрам обобщений.
Библиотека базовых классов .)((ЕТ (Вазе С!азз Ь(Ьгэгу — ВСЬ] включает удобные типы обобщенных делегатов, такие кан Ассьоп<> и ропе<>, которые применимы ко многим экземплярам, избавляя от обязанности определять собственные типы делегатов. Делегаты Асозоп<> можно использовать для хранения методов, принимающих до 16 параметров и не возвращающих значения, а делегаты ропе<> применять для хранения методов, принимающих до 16 параметров и возвращающих значение. Нв заметку! До появления .МЕТ 4.0 ВСЬ делегаты Асо1оп<> и топо<> принимали только до четырех параметров, а теперь — до шестнадцати. 340 Глава 11 Начиная с .)))ЕТ 4.0.
эти обобщенные делегаты также должны быть соответствующим образом помечены для вариантности. То есть две версии параметров будут выглядеть так: рцЫТс ое1едаге чоьп Асг1он< Тц Т1, Тц Т2 >( Т1 агд1, Т2 агд2 ); РЦЫТС С)Е1ЕОасэ ТВЕВЦ11 ГЦЦС< 1 Ц Т1, Гв Т2, ОЦГ ТВЕВЦ11> ( Т1 атд1, Т2 НГЯ2 ) ) Теперь в качестве примеров вариантности делегатов рассмотрим следующую иерархию типов; с1авв Ан1та1 ( ) с1авв Род: Ац1ив1 ) Предположим, что имеется следующая пара методов, определенных в некотором классе; вснс1с чогб ЯоиеГццотоп( Ац1иа1 аньиа1 вгашс чогб Аногпегрццсгьоп( Род сод ); Поскольку сигнатура функции соответствует сигнатуре делегата, возможность приведенного ниже присваивания яоиеГцнссьоп экземпляру Ассьон<Ацьиа1> имеет смысл: Асггоц<Аньиа1> ассьоц1 = яоиегццсс1опу Когда вызывается нсс1оц1, ему можно передать Рос или Ан1ие1, поскольку Рос неявно преобразуется в Аптпа1.
Давайте представим, что позже создан экземпляр АСГ1оц<Род> следующим образом: Асг1оп<Род> всоьоп2 = Апооьеггццсггоц! При вызове асстоп2 следует передавать экземпляр Род.Но также обратитевниманне, что поскольку экземпляр Род можно передать и Яотерццс11оп, то можно было бы создать ассьоц2 следующим образом: Асгьоо<эоя> асо1он2 = Яоиегццсс1оп~ Этоттип вариантногоприсваивания(в данном случае†контравариантного)группы методов экземпляру делегата молча поддерживается в С() уже некоторое время. Поэтому, если возможно показанное выше присваивание, то имеет смысл, чтобы была возможность и делать приведенное ниже присваивание, что и реализовано в версии СЗ 4.0: Асгтоп<эод> зсгьон2 = всс1оп1; Теперь рассмотрим краткий пример контравариантного присваивания АсЫон<Т> с использованием той же иерархии объектов, что и в приведенном выше примере: евана Яувсесн с1ввв Аньтэ1 ( рцЫ1с чтгеца1 чогб ЯьонАГГесгьон() ( Соцво1е.игьсе11не( "Реакция неизвестна" ) ) с1ввв Род: Аньиа1 ( рцЫТс очегггбе чогб Я)>онхггесс1оп() ( Сонво1е.кгьгешое( "Виляние хвостом..." Обобщения 341 зоасзс с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, Интересные моменты начинаются в следующей строке Ма1п.
Здесь экземпляр Асс1оп<Ап1ша1> присваивается ссылке на Асс1оп<Род>. И поскольку Род неявно преобразуется к Апзша1. а Асб1оп<Ап1ша1> неявно преобразуется к Асг1оп<Род>, то такое присваивание является контравариантным. В случае непонимания, каким образом тип Асс1оп<Ап1ша1> оказывается неявно преобразуемым в Асс1оп<Род>, если Апзша1 не может быть неявно преобразовано к Род, попробуйте сосредоточиться на действии.
Если действие может оперировать экземплярами Ап1ша1, оно определенно может работать и с энземплярами Род. Давайте пойдем еще дальше. В дисциплинах функционального программирования принято передавать одни функции в качестве параметров другим функциям. За счет использования делегатов делать это всегда было просто, а в главе 15 будет показано, что лямбда-выражения дополнительно упрощают задачу. Функции, ноторые принимают в качестве параметров другие функции, часто называют функциями высшего порядка или функционалами.















