📚 Hub Books: Онлайн-чтение книгРазная литератураИнтернет-журнал "Домашняя лаборатория", 2007 №9 - Журнал «Домашняя лаборатория»

Интернет-журнал "Домашняя лаборатория", 2007 №9 - Журнал «Домашняя лаборатория»

Шрифт:

-
+

Интервал:

-
+
1 ... 354 355 356 357 358 359 360 361 362 ... 415
Перейти на страницу:
class="p1">Итак, вызов конструктора — потомка приводит к цепочке вызовов конструкторов — предков, заканчивающейся вызовом конструктора прародителя. Затем в обратном порядке создаются объекты, начиная с объекта прародителя, и выполняются тела соответствующих конструкторов, инициализирующие поля и выполняющие другую работу этих конструкторов. Последним создается объект потомка и выполняется тело конструктора потомка.

Добавление методов и изменение методов родителя

Потомок может создать новый собственный метод с именем, отличным от имен наследуемых методов. В этом случае никаких особенностей нет. Вот пример такого метода, создаваемого в классе Derived;

public void DerivedMethod()

{

    Console.WriteLine ("Это метод класса Derived");

}

В отличие от неизменяемых полей классов — предков, класс — потомок может изменять наследуемые им методы. Если потомок создает метод с именем, совпадающим с именем метода предков, то возможны три ситуации:

• перегрузка метода. Она возникает, когда сигнатура создаваемого метода отличается от сигнатуры наследуемых методов предков. В этом случае в классе потомка будет несколько перегруженных методов с одним именем, и вызов нужного метода определяется обычными правилами перегрузки методов;

• переопределение метода. Метод родителя в этом случае должен иметь модификатор virtual или abstract. Эта наиболее интересная ситуация подробно будет рассмотрена в следующих разделах нашей лекции. При переопределении сохраняется сигнатура и модификаторы доступа наследуемого метода;

• скрытие метода. Если родительский метод не является виртуальным или абстрактным, то потомок может создать новый метод с тем же именем и той же сигнатурой, скрыв родительский метод в данном контексте. При вызове метода предпочтение будет отдаваться методу потомка, а имя наследуемого метода будет скрыто. Это не означает, что оно становится недоступным. Скрытый родительский метод всегда может быть вызван, если при вызове уточнить ключевым словом base имя метода.

Метод потомка, скрывающий метод родителя, следует сопровождать модификатором new, указывающим на новый метод. Если этот модификатор опущен, но из контекста ясно, что речь идет о новом методе, то выдается предупреждающее сообщение при компиляции проекта.

Вернемся к нашему примеру. Класс Found имел в своем составе метод Analysis. Его потомок класс Derived создает свой собственный метод анализа, скрывая метод родителя:

new public void Analysis()

{

   base.Analysis ();

   Console.WriteLine("Сложный анализ");

}

Если модификатор new опустить, он добавится по умолчанию с выдачей предупреждающего сообщения о скрытии метода родителя. Как компилятор узнает, что в этой ситуации речь идет о новом методе? Причины понятны. С одной стороны, родительский метод не имеет модификаторов virtual или abstract, поэтому речь не идет о переопределении метода. С другой стороны, в родительском классе уже есть метод с данным именем и сигнатурой, и поскольку в классе не могут существовать два метода с одинаковой сигнатурой, то речь может идти только о новом методе класса, скрывающем родительский метод. Несмотря на "интеллект" транслятора, хороший стиль программирования требует явного указания модификатора new в подобных ситуациях.

Заметьте, потомок строит свой анализ на основе метода, наследованного от родителя, вызывая первым делом скрытый родительский метод.

Рассмотрим случай, когда потомок добавляет перегруженный метод. Вот пример, когда потомок класса Derived — класс ChildDerived создает свой метод анализа, изменяя сигнатуру метода Analysis;

public void Analysis(int level)

{

   base.Analysis ();

   Console.WriteLine ("Анализ глубины {0}", level);

}

Большой ошибки не будет, если указать модификатор new и в этом случае, но будет выдано предупреждающее сообщение, что модификатор может быть опущен, поскольку сокрытия родительского метода не происходит.

Статический контроль типов и динамическое связывание

Рассмотрим семейство классов A1, А2… An, связанных отношением наследования. Класс Ak+1 является прямым потомком класса Аk. Пусть создана последовательность объектов x1, х2… хk, где хk — это объект класса Аk. Пусть в классе A1 создан метод M с модификатором virtual, переопределяемый всеми потомками, так что в рамках семейства классов метод M существует в n-формах, каждая из которых задает реализацию метода, выбранную соответствующим потомком. Рассмотрим основную операцию, инициирующую объектные вычисления — вызов объектом метода класса:

x1.M(arg1, arg2… argN)

Контролем типов называется проверка каждого вызова, удостоверяющая, что:

• в классе A1 объекта x1 действительно имеется метод M;

• список фактических аргументов в точке вызова соответствует по числу и типам списку формальных аргументов метода M, заданного в классе A1.

Язык С#, как и большинство других языков программирования, позволяет выполнить эту проверку еще на этапе компиляции и в случае нарушений выдать сообщение об ошибке периода компиляции.

Контроль типов, выполняемый на этапе компиляции, называется статическим контролем типов. Некоторые языки, например Smalltalk, производят этот контроль динамически — непосредственно перед выполнением метода. Понятно, что ошибки, обнаруживаемые при динамическом контроле типов, трудно исправимы и потому приводят к более тяжелым последствиям. В таких случаях остается уповать на то, что система тщательно отлажена, иначе непонятно, что будет делать конечный пользователь, получивший сообщение о том, что вызываемого метода вообще нет в классе данного объекта.

Перейдем к рассмотрению связывания. Напомним, что в рассматриваемом семействе классов метод M полиморфен: имея одно и то же имя и сигнатуру, он существует в разных формах — для каждого класса задана собственная реализация метода. С другой стороны, из-за возможностей, предоставляемых односторонним присваиванием, в точке вызова неясно, с объектом какого класса семейства в данный момент связана сущность x1 (вызову мог предшествовать такой оператор присваивания if (B) x1 = хk;).

Статическим связыванием называется связывание цели вызова и вызываемого метода на этапе компиляции, когда с сущностью связывается метод класса, заданного при объявлении сущности.

Динамическим связыванием называется связывание цели вызова и вызываемого метода на этапе выполнения, когда с сущностью связывается метод класса объекта, связанного с сущностью в момент выполнения.

При статическом связывании метод выбирается из класса сущности, при динамическом — из класса объекта, связанного с сущностью. Понятно, что на этапе компиляции возможно только статическое связывание, поскольку только в период выполнения можно определить, с объектом какого класса связана данная сущность. Это может быть класс любого из потомков класса сущности.

Какой же из видов связывания следует применять? Статическое связывание более эффективно в реализации, поскольку может быть сделано на этапе компиляции, так что при выполнении не потребуется никаких проверок. Динамическое связывание требует накладных расходов в период выполнения. Однако во многих случаях преимущества динамического связывания столь значительны, что о затратах не стоит и беспокоиться.

Уже достаточно давно разработан эффективный механизм реализации динамического связывания. Еще на этапе компиляции подготавливается так называемая таблица виртуальных методов, содержащая их адреса. Связывание объекта хk с принадлежащим ему методом Mk производится

1 ... 354 355 356 357 358 359 360 361 362 ... 415
Перейти на страницу:

Комментарии

Обратите внимание, что комментарий должен быть не короче 20 символов. Покажите уважение к себе и другим пользователям!

Никто еще не прокомментировал. Хотите быть первым, кто выскажется?