Нэш Трей - C# 2010. Ускоренный курс для профессионалов (2010) (1160865), страница 42
Текст из файла (страница 42)
Роногх. Такое поведение не интуитивно для объектно-ориентированных систем. Только потому, что язык позволяет вам делать что-то, это вовсе не означает, что делать зто — правильно. Теперь вы видите, зачем прежде всего необходимы предупреждения компилятора. В следующем примере 1Ео№ГВох по той или иной причине должен объявлять метод Ра1пг,чья сигнатура в точности совпадает с сигнатурой метода нз 1Р1сопгго1. Поэтому здесь необходимо использовать ключевое слово пеи: ця1пс Яуяпепи рцЬ1гс гппегпасе 1Р1соппго1 ( тога Разик()г ) 154 Глава о рпЬ11с 1псеггвсе 1ЕоэсВох: 1С1Сопгго1 ( пев чогб Рэвпг() ) ) роватс Епгеггвсе 1огорьтвс: 1П1Сопгго1 ) РпЬ11с с1ввв СоэооВох: 1ЕЙРГВох, 1пгор11вс ( рсв11с тото Равпг() ( Сопва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Сопсго1. Когда интерфейс 1ес)ЕЬВох объявляет метод Ра1пс, используя ключевое слово пею тем самым он скрывает метод Ра1пс, объявленный в 1()1сопсго1.
Когда вы вызываете СоэЬоВох.Раапс, то вызывается метод 1Ег)ЕСВох.Ра1пс.как если выбран 1ЕбЕСВохпуть в иерархии наследования. По сути, в любой момент, когда любой путь скрывает метод, то он скрывает метод для всех путей. Это станет понятнее, когда я расскажу, как компилятор находит соответствие конкретного метода методу интерфейса, когда вызывается метод интерфейса. Этот процесс называется огпображением госпе)крейга, и я расскажу о нем далее в разделе "Правила сопоставления членов интерфейсов" настоящей главы.
Реализация интерфейсов При реализации интерфейсов в СВ вы должны выбрать его реализацию одним иэ двух способов. По умолчанию реализации интерфейсов являются неявными реализациями. Реализация метода — часть общедоступного контракта класса, но также она реализует интерфейс неявно. Альтернативно вы можете реализовать интерфейс явным образом, причем реализации метода является приватной по отношению к реализующему классу и не становится частью общедоступного интерфейса. Явная реализация обеспечивает некоторую гибкость.
особенно при реализации двух интерфейсов с одноименными методами в них. Интерфейсы и контракты 185 Неявная реализация интерфейса Когда конкретный тип реализует методы наследуемых интерфейсов, и эти методы помечены как риЫ го, ато известно как неявная реализация инптер4ебса. Что хорошего, когда конкретный тип реализует контракт определенного интерфейса, а потребитель объектов этого типа не может вызвать методы этого контракта? Например, следующий код некорректен: РиЬ1гс Тпкеггасе 1П1Сопкго1 гоаб Рз1пг О ) ) риЫТс с1аяя ЯкакьсТехс: 1П1Сопкго1 ( гоаб Раапк(); УУ !!! не кОмпилиРУетсЯ !!! ) Если вы попытаетесь скомпилировать это, компилятор немедленно пожалуется на то, что класс ЯгаЫстехс не реализовал все методы унаследованного интерфейса — в данном случае, 1О1сопсго1.
Чтобы это заработало, вы должны переписать код следующим образом: риЬ11с 1псеггасе 1О1Сопкго1 ( иогб Ра1пГ(); ) риЫТс с1аяя ЯсзГ1стехс: 1П1Сопего1 ( риЬ1гс ио16 Ра1пс О ~ //К объявлению метода добавлено 'риЫ1с' Теперь код не только скомпилируется, но при вызове Раьпг через ссылку на ЯсасьсТехс нли через ссылку на 1ПТСопгго1, будет вызываться метод ягас1стехЫ Ра1пс. Таким образом, потребители могут трактовать экземпляры ЯгагасТехг полиморфно как экземпляры 1П1Сопсго1. Явная реализация интерфейса Когда конкретный тип реализует интерфейс явно, его методы также становятся частью общедоступного контракта самого конкретного типа. Однако не всегда нужно, чтобы методы реализации интерфейса становились частью общедоступного интерфейса класса.
реализующего этот интерфейс. Например, класс яуясев. 10. Р11еяггезв реализует 101ярояаЫе, но вы не должны вызывать Езяроее через экземпляр Р11еяггезв. Вместо этого вы должны сначала выполнить приведение ссылки на объект Р11езсгеав к интерфейсу 1П1ярояаЫе, а затем можно вызывать О).ярояе.
Когда вам понадобится такое поведение для ваших собственных типов, вы должны реализовать интерфейсы, используя явную реализацию. На заметку! Чтобы достичь того же результата, что от О1ярояе, но через ссылку на объект Р11еяьгеав, вы должны вызвать Р11еясгеав. с1ояе. В реализации Р11еяьгеав. с1ояе вызывается напрямую внутренняя реализация метода паярояе, Зачем разработчикам Р11езггеав это понадобилось? Скорее всего, потому, что в лингвистическом отношении "с!ове" (закрыть) по отношению к файлу более осмысленно, чем "б!ярозе ор (освободить). 156 Глава 5 Вы можете также испольэовать явную реализацию для предоставления отдельных реализаций перекрывающихся методов из унаследованных интерфейсов.
Давайте еще раз вернемся к примеру СовЬовох из предыдущего раздела. Если вы хотите представить отдельные реалиэащии для 1ет(1гвох.Ватаги 101сопгго1.ра1пг внутри сокьовох, то для этого нужно использовать явную реализацию интерфейсов, как показано ниже: пвтпо Яувтевп рпЬ11с Рптеггвсе 101сопсго1 ното Ратпс()> ) рпвтьс 1псеггасе 1ЕЙРГВох: 1В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Соппго1. Ратпс ()" ) ! рпвагс во1г( Ратпп () ( ( (1()1сопсго1) Гптв) . Рвтпс (); ) рпЬ11с с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. Ра1пс неявно, тогда мне не понадобилась бы третья реализация Ра1пс. Но в этом случае я полагаю, что реализация собственного метода Раьпг добавляет болыпе гибкости и более оправдана для Соп1ЬоВох, чтобы он мог использовать другую реализацию, в то же время Ннтерфейсы и контракты 157 добавив ей ценности. Если вы скомпилируете и запустите предыдущий пример, то уви- дите вывод, подобный следующему: СокЬоВох.101Соптго1.ра1пт() СоаЬоВох.1Е01СВох.ратпк() СокЬовох.101Соптго1.Ра1пт() СокьоВох.101сопкго1.ратпк() Конечно, этот пример довольно надуманный,но он предназначен для того, чтобы продемонстрировать сложность явных реализаций интерфейсов и сокрытие членов при множественном наследовании интерфейсов.
Переопределение реализаций интерфейсов в производных классах Предположим, что у вас есть удобная реализация Сол1ЬоВох, как в предмлущем разделе, и разработчик решил не герметизировать этот класс, чтобы вы могли наследоваться от него. На заметку! Я советую вам объявлять все свои классы как зеа1ег(, если только вы явно не собираетесь наследоваться от них. В главе 4 я объяснил подробно, почему зто желательно, Теперь представим, что вы создаете новый класс ГапсуСокЬоВох и хотите, чтобы он как-то иначе себя рисовал — может быть, в некоторой новой психоделической теме. Вы можете попробовать что-нибудь вроде следующего: пэ1по Яузтеко рпЬ11с ТпкегГасе 101Соптго1 ( чо10 Ратпг () ) чо16 Бион()1 ) рпЬ11с Тптегтасе 1ЕЙТСВох: 101Сопсго1 ( чо10 Бе1есСТехт() 1 ) рпЬ11с Тптегтасе 10гор01зт: 101Соптго1 ( чокб ЯЬоньазк(); ) рпЪ11с с1азз Соп1ЬоВох: 1ЕО1СВох, 10гор01зг ( рив11с чо1г( Ра1пт () ( ) рпЬ11с чойб Бион() ( ) рпЬ11с чо1г( Яе1есттехг () ( рпвтас чокб Бнон01зг() ( ) ) рпЬ11с с1азз ГапсусоаЬовох: СоаЬовох ( рпв11с чо1г( Ра1пС () ( ) ) рпЬ11с с1азз Ептгуро1пг ( эхагтс чо10 Матп() ГапсусоеЬовох сЬ = пен ГапсуСоаЬЬВох() ) 158 Глава б Однако компилятор предупредит вас о том, что РапсусотЬоВох.
Рауле скрывает сотьовох. Ра1пс, и что вы, возможно, подразумевали использование ключевого слова пен. Это покажется неожиданным, если вы предполагаете, что методы, реализующие методы интерфейса, должны быть автоматически виртуальными. В Се это не так. На заметку! "За кулисами" реализации методов интерфейсов вызываются так, будто они являются виртуальными.
любые реализации метода интерфейса, не помеченные чтгсиа1 в с», помечаются как чШиа1 и Рз па1 (герметизированный) в сгенерированном коде! С Если же метод помечен как чугеиа1 в Сй, то в сгенерированном коде (Ь он будет помечен как чугкиа1 и пенз1ос (новый). это может послужить причиной некоторой путаницы. Столкнувшись с подобной проблемой, у вас есть два выбора. Один — заново реализовать интерфейс 1В1Соппго1 в классе РапсуСопЬоВох: изтпо Яузгеки риЬ11с Епсеггасе 1С1Сопгго1 чокб Ратпт(); тото Япон(); ) риЫРс 1пкеггасе 1ЕС1СВох г 1О1Сопего1 чотс Яе1есстехс О г ) риЫЯс тптегтасе 1ргорьтзт г 1С1Соптго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СВох) сЬ) .










