Различное поведение перегрузки метода в С#

Я проходил через С# Brainteasers (http://www.yoda.arachsys.com/csharp/teasers.html) и наткнулся на один вопрос: какой должен быть выход этого кода?

class Base
{
    public virtual void Foo(int x)
    {
        Console.WriteLine ("Base.Foo(int)");
    }
}

class Derived : Base
{
    public override void Foo(int x)
    {
        Console.WriteLine ("Derived.Foo(int)");
    }

    public void Foo(object o)
    {
        Console.WriteLine ("Derived.Foo(object)");
    }
}

class Test
{
    static void Main()
    {
        Derived d = new Derived();
        int i = 10;
        d.Foo(i);  // it prints ("Derived.Foo(object)"
    }
} 

Но если я изменю код на

class Derived 
{
    public void Foo(int x)
    {
        Console.WriteLine("Derived.Foo(int)");
    }

    public void Foo(object o)
    {
        Console.WriteLine("Derived.Foo(object)");
    }
}

class Program
{
    static void Main(string[] args)
    {
        Derived d = new Derived();
        int i = 10;
        d.Foo(i); // prints  Derived.Foo(int)");

        Console.ReadKey();
    }
}

Я хочу, чтобы результат был изменен, когда мы наследуем vs, а не наследуем; почему перегрузка метода ведет себя по-разному в этих двух случаях?

Ответ 1

Как я указал на странице answers:

Derived.Foo(объект) печатается - при выборе перегрузки, если есть совместимые методы, объявленные в производном классе, все сигнатуры, объявленные в базовом классе, игнорируются - даже если они переопределены в том же производном классе!

Другими словами, компилятор рассматривает методы, которые только что объявлены в самом производном классе (на основе типа времени компиляции) и видят, применимы ли они. Если они есть, он использует "лучший" доступный. Если ни один из них не применим, он пробует базовый класс и так далее. Переопределенный метод не считается объявленным в производном классе.

Подробнее см. разделы 7.4.3 и 7.5.5.1 спецификации С# 3.

Теперь о том, почему именно так указано - я не знаю. Для меня имеет смысл, что методы, объявленные в производном классе, имеют приоритет над объявленными в базовом классе, поскольку в противном случае вы сталкиваетесь с проблемой "хрупкого базового класса" - добавление метода в базовый класс может изменить значение кода, используя производного класса. Однако, если производный класс переопределяет метод, объявленный в базовом классе, он четко осознает это, так что элемент хрупкости не применяется.

Ответ 2

Он вращается вокруг области. В первой программе void Foo (int i) принадлежит классу Base. class Derived просто переопределяет его поведение.

Foo (int i) игнорируется, поскольку он "заимствован" и переопределяется через наследование из класса Base. если Foo (объект o) не существует, тогда будет использоваться Foo (int i).

Во второй программе вызывается void Foo (int i), потому что она правильно принадлежит классу Derived (т.е. не заимствуется и переопределяется через наследование) и имеет наилучшую подгонку подписи.

Ответ 3

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

d.Foo(i);

Он пытается сопоставить подпись метода с вашим текущим классом (а не базовым классом). Он находит приемлемое соответствие Foo(object) и далее не рассматривает сигнатуры метода базового класса.

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

-Doug

Ответ 4

Реальный вопрос: почему дизайнеры С# полагали, что перегрузка разрешения должна игнорировать переопределенные методы в этой ситуации?

Именно такие проблемы делают метод перегрузкой одной из моих наименее любимых функций языка.