Методы new и override

Чтобы переопределить метод в производном классе, этот метод должен быть снабжен модификатором override. Если этого не сделать, то компилятор предупредит о необходимости указания в объявлении производного метода либо модификатора new, либо модификатора override. По умолчанию компилятор подразумевает использование модификатора new, что, вероятно, даст эффект, противоположный тому, что можно было ожидать.

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

К сожалению, очень часто приходится сталкиваться с плохо написанным кодом С++, в котором используется глубокая иерархия классов, где разработчики поленились снабдить виртуальные переопределяемые методы ключевым словом virtual. В таких случаях единственная возможность узнать, что конкретный метод переопределяет виртуальный метод базового класса — это заглянуть в объявление базового класса.

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

Взгляните на следующий код:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System;
public class A {
public virtual void SomeMethod()  {
Console.WriteLine ( "A.SomeMethod" );
}
}
public class В : A {
public void SomeMethod ()  {
Console.WriteLine ( "B.SomeMethod" );
}
}
public class EntryPoint {
static void Main()  {
В b = new В ();
A a = b;
a.SomeMethod();
}
}

Приведенный код компилируется, но при этом выдается следующее предупреждение:

test.cs (12,17) : warning CS0114: ‘В.SomeMethod()’ hides inherited member ‘A.SomeMethod()1.
To make the current member override that implementation, add the override keyword. Otherwise add the new keyword.
test.cs(12,17): предупреждение CS0114: В.SomeMethod() скрывает унаследованный член A. SomeMethod ().

Чтобы текущий член переопределил эту реализацию, добавьте ключевое слово override. В противном случае добавьте ключевое слово new.

При выполнении кода вызывается метод A. SomeMethod. Так что же делает ключевое слово new? Оно разбивает виртуальную цепочку в данной точке иерархии. Когда виртуальный метод вызывается через ссылку на объект, то конкретный вызываемый метод определяется по таблице методов во время выполнения. Если метод виртуальный, то исполняющая система движется по иерархии в поисках наиболее удаленной производной версии метода, и затем вызывает ее.

Однако если она во время поиска встречает метод, помеченный модификатором new, то возвращается к методу из предыдущего класса в иерархии и использует его. Таким образом, вызывается именно A. SomeMethod. Если бы метод В.SomeMethod был помечен ключевым словом override, то был бы вызван именно он. Поскольку в С# модификатор new применяется по умолчанию, когда не указан никакой другой, компилятор выдает предупреждение, чтобы привлечь внимание тех, кто привык к синтаксису С++.

И, наконец, модификатор new ортогонален по смыслу модификатору virtual — в том плане, что метод, помеченный как new, также может быть или не быть виртуальным. В предыдущем примере для метода В.SomeMethod не был указан модификатор virtual, так что не может быть такого, чтобы класс С, производный от В, переопределил В.SomeMethod, поскольку он не является виртуальным. Таким образом, ключевое слово new не только разрушает виртуальную цепочку, но также переопределяет то, получат ли данный класс и классы-наследники В виртуальный метод SomeMethod.

Другая сложность, о которой следует упомянуть в отношении переопределения методов — как и когда вызывать версию метода базового класса.

В С# вызвать версию базового класса можно с использованием идентификатора base, как показано ниже:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using System;
public class A {
public virtual void SomeMethod ()  {
Console.WriteLine ( "A.SomeMethod" );
}
}
public class В : A {
public override void SomeMethod ()  {
Console.WriteLine ( "B.SomeMethod" );
base.SomeMethod();
}
}
public class EntryPoint {
static void Main()  {
В b = new В () ;
A a = b;
a.SomeMethod();
}
}

Как и можно было ожидать, вывод приведенного кода напечатает A. SomeMethod в строке, следующей после вывода В. SomeMethod. Является ли такой порядок событий правильным? Не должно ли быть все наоборот? Не должен ли метод В. SomeMethod вызвать версию базового класса перед тем, как выполнить свою работу? Дело в том, что для ответа на этот вопрос не достаточно информации.

Здесь имеется проблема с наследованием и переопределением виртуальных методов. Как узнать, когда следует вызывать метод базового класса, и нужно ли это делать? Ответ заключается в том, что метод должен быть соответствующим образом документирован, чтобы можно было принять правильное решение.

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

Например, если вы следуете шаблону не виртуального интерфейса (Non-Virtual Interface — NVI), то виртуальный метод, находящийся под вопросом, будет объявлен как protected, и тогда понадобится документировать как общедоступные методы, так и некоторые защищенные, и виртуальные методы должны ясно устанавливать, должен ли базовый класс вызывать их, и когда.

Вы можете следить за любыми ответами на эту запись через RSS 2.0 ленту. Вы можете оставить ответ, или trackback с вашего собственного сайта.

Оставьте отзыв

XHTML: Вы можете использовать следующие теги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

 
Rambler's Top100