Нэш Трей - C# 2010. Ускоренный курс для профессионалов (2010) (1160865), страница 38
Текст из файла (страница 38)
После того, как выражения вычислены — в данном случае, после вызовов Сепегзгеча1ие1 и Вепегасеча1ие2— аргументы помещаются в вх соответствующие позиции, чтобы найти лучший метод. Внимание! До появления именованных аргументов можно было писать код, в котором имел значение порядок вычисления выражений в списках параметров, Этот подход нельзя назвать удачным, как с именованными аргументами, так и без них. Предположим, что в предыдущем примере методы закодированы с побочным эффектом — исходя из того, что Сепегагеча1ие1 будет всегда вызываться перед яепегасеча1ие2. далее предположим, что метод А. ВоБогзесп1пд был вызван с использованием позиционных аргументов, когда текущей была версия С№ 3.0.
Позднее, после появления именованных аргументов, инженер службы поддержки решил изменить код и передать аргументы в противоположном порядке, применив именованные аргументы — просто потому, что это дает более симпатично выглядящий код. Сразу же возникнет серьезная проблема! Мораль этой истории в том, что следует избегать ситуации, когда приходится полагаться на определенный порядок вычисления выражений в списке аргументов.
Наследование и виртуальные методы Понятие виртуальных методов реализовано в С№ так же, как в языках С++ и дача. Здесь вообще нет никаких новшеств, поскольку С№ — объектно-ориентированный язык. а виртуальные методы представляют собой главный механизм реализации динамического полиморфизма. Тем не менее, некоторые заметные отличия между этими языками требуют специального упоминания.
Виртуальные и абстрактные методы Виртуальный метод объявляется с использованием в месте его объявления модификатора нагсца1 или з)ээггасщ Оба модификатора вводят метод в пространство объявления как таковой, что может быть переопределен в производных классах. Отличие между этими двумя модификаторами в том. что абстрактные методы обязательно должны быть переопределены, в то время как виртуальные методы — нет.
Абстракгные методы подобны чистым виртуальным методам С++, за исключением того. что чистые виртуальные методы С++ могут иметь ассоциированную с ними реализацию, в то время как абстрактные методы С№ не могут. Вдобавок классы, которые содержат абстрактные методы, сами должны быть помечены как абстрактные. Виртуальные методы. в отличие Классы, структуры и объекты 141 от абстрактных, обязаны иметь ассоциированную с ними реализацию. Виртуальные методы наряду с их интерфейсами — единственные средства реализации полиморфизма в С№. На заметку! "За кулисами" СЬН реализует виртуальные методы иначе, чем в С++.
В то время как С++ может создавать множество таблиц виртуальных методов (динамических таблиц, содержащих указатели на виртуальные методы) для индивидуального объекта класса в зависимости от их статической иерархической структуры, объекты СЕВ имеют лишь одну таблицу методов, содержащую как виртуальные, так и не виртуальные методы. Кроме того, эта таблица в СЕВ строится на ранней стадии жизненного цикла объекта. При этом порядок создания объектов не только влияет на порядок вызова статических инициализаторов и конструкторов в иерархии, но он также обеспечивает для С№ такую возможность, которой нет в С++, Подробнее о том, как СЕВ управляет таблицами методов для экземпляров объектов, читайте в книге Дона Бокса (Ооп Вох) и Криса Селлса (Слпз ЗеИв) ЕззепИа) . (УЕТ, Уо(ите (: Тле Соттоп 1апдивде Випбте (Абб(зоп-УУез)еу Рго(езз)опа!, 2002 г.). Методы пем и очесах л.сне Чтобы переопределить метод в производном классе, этот метод должен быть снабжен модификатором очеггапе.
Если этого не сделать, то компилятор предупредит о необходимости указания в объявлении производного метода либо модификатора пеи. либо модификатора очегггс(е. По умолчанию компилятор подразумевает использование модификатора пеи, что, вероятно, даст эффект, противоположный тому, что можно было ожидать. Это поведение отличается от принятого в Се ь, поскольку если метод С+ь помечен как эагв ца1, то любой производный метод с тем же именем и сигнатурой автоматически переопределяет этот виртуальный метод, и модификатор ч1 г го я 1 в таких производных методах совершенно не обязателен. В целом то, что С№ требует пометки переопределяющего метода, следует считать положительным моментом, так как это улучшает читабельность кода.
К сожалению, очень часто приходится сталкиваться с плохо написанным кодом С++, в котором используется глубокая иерархия классов, где разработчики поленились снабдить виртуальные переопределяемые методы ключевым словом тг1ггца1. В таких случаях единственная возможность узнать, что конкретный метод переопределяет виртуальный метод базового класса — это заглянуть в объявление базового класса.
В особенно глубоких иерархиях классов в поисках ответа приходится продираться буквально через горы файлов. В языке С№ данная проблема решена. Взгляните на следующий код: пя1пд Яузсев) роЬ11с с1зяв А ( риьтьс таггоз1 чо1г( Яоиеиегпог(() ( Сопзо1е.нг1ке11пе( "А.эовенеспог(" ) ) ) рпЬ11с с1яяя В: А ( риЬ11с чоап Яоиенегпоп() ( Сопяо1е.игьгеькпе( "В.зоиенеекос" )г ) роЬ11с с1азя Епггуроапт. ( 142 Глава 4 всасус чотб Маьп() ( В Ъ = пеы В(); Ая=Ь; а.эоаенеСЬоб()с Приведенный код компилируется, но при этом выдается следующее предупреждение: Сеяс.ся(12,17)с иягптпц СЯ0114: 'В.ЯоюенеСЬоб()' Ыбез 1ппег1себ юеаьег 'А.ЯотеМеспоб О '. То пе)се СЬе соггепс теапег очегггбе СЬяс ппр1еаепсястоп, ябб СЬе очегг1бе )сеуногб.
ОСЬегиуве ябб СЬе пеи )сеуиогб. СеяС.ся(12,17)с предупрехдение СЯР114с Я.ЯоиеМеСЛобП скрывает унаследованный член А.ЯоюенеСЛоб П . Чтобы текуюий член переопределил зту реализацию, добавьте ключевое слово оуеггббе. В противном случае добавьте ключевое слово пен.
При выполнении кода вызывается метод А. Болсенеспоб. Так что ясе делает ключевое слово пеыУ Оно разбивает виртуальную цепочку в данной точке иерархии. Когда виртуальный метод вызывается через ссылку на объект, то конкретный вызываемый метод определяется по таблице методов во время выполнения. Если метод виртуальный, то исполняющая система движется по иерархии в поисках наиболее удаленной производной версии метода, н затем вызывает ее, Однако если она во время поиска встречает метод. помеченный модификатором пеы, то возвращается к методу нз предыдущего класса в иерархии и использует его. Таким образом, вызывается именно А.
Яопсенеспоб. Если бы метод В. Яотенеспоб был помечен ключевым словом очеггус)е, то был бы вызван именно он. Поскольку в Са модификатор пеи применяется по умолчанию, когда не указан никакой другой, компилятор выдает предупреждение, чтобы привлечь внимание тех, кто привык к синтаксису С++. И, наконец, модификатор пеы оргогонален по смыслу модификатору ч).гспа1 — в том плане, что метод, помеченный как пеы, также может быть или не быть виртуальным. В предыдущем примере для метода В.
Боюенеспоб не был указан модификатор чугспа1, так что не может быть такого, чтобы класс С, производный от В, переопределил В. БолсеМеспоб, поскольку он не является виртуальным. Таким образом, ключевое слово пеи не только разрушает виртуальную цепочку, но также переопределяет то, получат ли данный класс и классы-наследники В виртуальный метод Яоюенеспоб. Другая сложность, о которой следует упомянуть в отношении переопределения методов — как и когда вызывать версию метода базового класса.
В С» вызвать версию базового класса можно с использованием идентификатора Ьаяе, как показано ниже: овапч Яуясепц рсЬ11с с1язя А ( роЫСс чагспя1 чоуб БовеМеСЬоб() ( Сопво1е.вгусеьупе( "А.ЯоюемеСЬоб" )а ) риЬ11с с1явв В: А ( рпЫТс очеггсбе чоаб БоаенеСЬоб() ( Сопво1е .Кг1се11пе ( "В .ЯотеМеСЬоб" ) Ьазе.эоюеМеСЬоб О; ) ) Классы, структуры и объекты 143 риьаас с1авв ЕпсгуРоапк ( вкакас ттотб Мвтп() ( В Ь = пеи В() т А в = Ь! в.аопенеспос( О ) ) ) Как и можно было ожидать. вывод приведенного кода напечатает А. Вослемеспос) в строке, следующей после вывода В.
Вопеиегпос). Является ли такой порядок событий правильным? Не должно ли быть все наоборот? Не должен ли метод В. Воаемеспос( вызвать версию базового класса перед тем, как выполнить свою работу? Дело в том, что для ответа на этот вопрос не достаточно информации. Здесь имеется проблема с наследованием и переопределением виртуальных методов. Как узнать. когда следует вызывать метод базового класса, и нужно ли это делать? Ответ заключается в том, что метод должен быть соответствующим образом документирован, чтобы можно было принять правильное решение. Таким образом, наследование с виртуальными методами повышает рабочую нагрузку за счет обязательного документирования, поскольку потребителей класса потребуется снабдить информацией, выходящей за рамки простого общедоступного интерфейса. Например, если вы следуете шаблону не виртуального интерфейса (Хоп-т?)гтпа) 1п1егуасе — )т)Л), который будет описан в главе 13, то виртуальный метод, находящийся под вопросом, будет объявлен как ргосессес(, и тогда понадобится документировать как общедоступные методы.
так и некоторые защищенные, и виртуальные методы должны ясно устанавливать, должен ли базовый класс вызывать их, и когда. Методы зеа1ес1 По причинам, установленным ранее, классы по умолчанию должны быть герметизированы (ве а1ес(), а наследование может быть разрешено только в хорошо продуманных случаях. Сколько раз приходится наблюдать иерархии, при создании которых разработчик думал: "Сделаю я, пожалуй, все методы виртуальными, чтобы обеспечить максимальную гибкость для производных классов". Все, чего он добивался — это создание целого набора вложенных ошибок, которые проявлялись позднее.
Такой образ мышления характерен для менее опытных проектировщиков, захваченнътх сложностью иерархии и виртуальных методов. Тот факт. что наследование сопровождается виртуальными методами, настолько неожиданно сложен, что лучше явно отключать эту возможность, чем открывать ее для злоупотребления. Таким образом, при проектировании классов следует отдавать предпочтение созданию герметизированных, ненаследуемых классов и тщательно документировать общедоступный интерфейс. Потребители, которым понадобится расширить функциональность, смогут это сделать, но не через наследование, а через включение.
Расширение включением [сон(а(пешепт) в сопровождении изощренных определений интерфейсов намного мощнее, чем наследование классов. В редких случаях требуется выполнить наследование от класса с виртуальными методами и завершить виртуальную цепочку своим переопределением. Другими словами, необходимо запретить производным классам дальнейшее переопределение виртуального метода.
Чтобы сделать это. метод помечается как гермегизированный с помощью модификатора веа1ес(. Такая пометка означает, что ни один производный класс не сможет переопределить данный метод. Однако, как говорилось в предыдущем разделе, можно предоставить метод с той же сигнатурой, если он помечен модификатором пеи. Фактически можно было бы пометить новый метод как тлй гспа1, тем самым начав новую виртуальную цепочку в иерархии.
Это не то же самое. что герметизация всего 144 Глава 4 класса, которая вообще запрещает дальнейшее наследование от этого класса. Если производный класс помечен как зез1ео, то снабжение его методов модификатором зеа1еб является излишним. Завершающие замечания о виртуальных методах СФ Ясно, что в СЭ предусмотрено множество гибких ключевых слов, позволяющих делать некоторые интересные вещи с наследованием и виртуальными методами.











