Перегрузка базового метода в производном классе

Итак, я играл с С#, чтобы узнать, соответствует ли это поведение С++ из этого сообщения: 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) удаляется из набора.