Перегрузка базового метода в производном классе
Итак, я играл с С#, чтобы узнать, соответствует ли это поведение С++ из этого сообщения: http://herbsutter.com/2013/05/22/gotw-5-solution-overriding-virtual-functions/
когда я столкнулся с этим очень странным поведением:
public class BaseClass
{
public virtual void Foo(int i)
{
Console.WriteLine("Called Foo(int): " + i);
}
public void Foo(string i)
{
Console.WriteLine("Called Foo(string): " + i);
}
}
public class DerivedClass : BaseClass
{
public void Foo(double i)
{
Console.WriteLine("Called Foo(double): " + i);
}
}
public class OverriddenDerivedClass : BaseClass
{
public override void Foo(int i)
{
base.Foo(i);
}
public void Foo(double i)
{
Console.WriteLine("Called Foo(double): " + i);
}
}
class Program
{
static void Main(string[] args)
{
DerivedClass derived = new DerivedClass();
OverriddenDerivedClass overridedDerived = new OverriddenDerivedClass();
int i = 1;
double d = 2.0;
string s = "hi";
derived.Foo(i);
derived.Foo(d);
derived.Foo(s);
overridedDerived.Foo(i);
overridedDerived.Foo(d);
overridedDerived.Foo(s);
}
}
Выход
Called Foo(double): 1
Called Foo(double): 2
Called Foo(string): hi
Called Foo(double): 1
Called Foo(double): 2
Called Foo(string): hi
Таким образом, очевидно, что это подразумевает, что неявно преобразованный int удваивает более специфический Foo (int) из базового класса. Или он скрывает Foo (int) от базового класса? Но тогда: почему не скрывается Foo (строка)? Чувствует себя очень непоследовательно... Также не имеет значения, переопределяю ли я Foo (int) или нет; результат тот же. Может ли кто-нибудь объяснить, что здесь происходит?
(Да, я знаю, что неверно перегружать базовые методы в производном классе - Liskov и все - но я все еще не ожидал, что Foo (int) в OverriddenDerivedClass не будет вызываться?!)
Ответы
Ответ 1
Чтобы объяснить, как это работает для примера OverriddenDerivedClass
:
Посмотрите на спецификацию С# для поиска членов здесь: http://msdn.microsoft.com/en-us/library/aa691331%28VS.71%29.aspx
Это определяет, как выполняется поиск.
В частности, посмотрите на эту часть:
Сначала создается множество всех доступных (раздел 3.5) элементов с именем N, объявленным в T, и базовыми типами (раздел 7.3.1) T. Объявления, содержащие модификатор переопределения, исключаются из набора.
В вашем случае N
есть Foo()
. Из-за Declarations that include an override modifier are excluded from the set
тогда override Foo(int i)
исключается из набора.
Следовательно, остается только неперекрываемое Foo(double i)
, и, следовательно, это тот, который вызывается.
Вот как это работает для примера OverriddenDerivedClass
, но это не объяснение примера DerivedClass
.
Чтобы объяснить это, посмотрите на эту часть спецификации:
Затем члены, которые скрыты другими членами, удаляются из набора.
Foo(double i)
в DerivedClass
скрывает Foo(int i)
от базового класса, поэтому он удаляется из набора.
Трудная вещь здесь - это часть, которая гласит:
Все методы с той же сигнатурой, что и M, объявленные в базовом типе S, удаляются из набора.
Вы можете сказать: "Но подождите! Foo(double i)
не имеет такой же подписи, что и Foo(int i)
, поэтому его нельзя удалять из набора!".
Однако, поскольку существует неявное преобразование из int в double, считается, что оно имеет одну и ту же подпись, поэтому Foo(int i)
удаляется из набора.