Подтвердить что ты не робот

Разрешение перегрузки и виртуальные методы

Рассмотрим следующий код (он немного длинный, но, надеюсь, вы можете следовать):

class A
{
}

class B : A
{
}

class C
{
    public virtual void Foo(B b)
    {
        Console.WriteLine("base.Foo(B)");
    }
}

class D: C
{
    public override void Foo(B b)
    {
        Console.WriteLine("Foo(B)");
    }

    public void Foo(A a)
    {
        Console.WriteLine("Foo(A)");
    }
}

class Program
{
    public static void Main()
    {
        B b = new B();
        D d = new D ();
        d.Foo(b);
    }
}

Если вы считаете, что выход этой программы является "Foo (B)", то вы будете в той же лодке, что и я: совершенно неправильно! Фактически, он выводит "Foo (A)"

Если я удалю виртуальный метод из класса C, он будет работать так, как ожидалось: "Foo (B)" - это вывод.

Почему компилятор выбирает версию, которая принимает A, когда B является более производным классом?

4b9b3361

Ответ 1

Ответ указан в спецификации С# раздел 7.3 и раздел 7.5. 5.1

Я сломал шаги, используемые для выбора метода для вызова.

  • Сначала создается набор всех доступных элементов с именем N (N=Foo), объявленный в T (T=class D), и базовые типы T (class C). Объявления, содержащие модификатор переопределения, исключаются из набора (исключается D.Foo(B))

    S = { C.Foo(B) ; D.Foo(A) }
    
  • Создан набор методов-кандидатов для вызова метода. Начиная с набора методов, связанных с M, которые были найдены при поиске предыдущего элемента, набор сводится к тем методам, которые применимы относительно списка аргументов AL (AL=B). Снижение набора состоит в применении следующих правил к каждому методу T.N в множестве, где T (T=class D) - это тип, в котором объявлен метод N (N=Foo):

    • Если N не применимо относительно AL (Раздел 7.4.2.1), то N удаляется из набора.

      • C.Foo(B) применим в отношении AL
      • D.Foo(A) применим относительно AL

        S = { C.Foo(B) ; D.Foo(A) }
        
    • Если N применимо относительно AL (раздел 7.4.2.1), то все методы, объявленные в базовом типе T, удаляются из набора. C.Foo(B) удаляется из набора

          S = { D.Foo(A) }
      

В конце победитель D.Foo(A).


Если абстрактный метод удаляется из C

Если абстрактный метод удален из C, начальный набор S = { D.Foo(B) ; D.Foo(A) } и правило разрешения перегрузки должно использоваться для выбора лучший член функции в этом наборе.

В этом случае победитель D.Foo(B).

Ответ 2

Почему компилятор выбирает версию, которая принимает A, когда B - это более производный класс?

Как отмечали другие, компилятор делает это, потому что это говорит о том, что говорит спецификация языка.

Это может быть неудовлетворительный ответ. Естественным продолжением было бы "какие принципы дизайна были в основном решением указать язык таким образом?"

Это часто задаваемый вопрос, как в StackOverflow, так и в моем почтовом ящике. Краткий ответ: "Этот проект смягчает семейство ошибок класса Brittle Base Class".

Описание функции и ее описание так, как описано в моей статье на эту тему:

http://blogs.msdn.com/b/ericlippert/archive/2007/09/04/future-breaking-changes-part-three.aspx

Дополнительные статьи о том, как различные языки относятся к проблеме с хрупким базовым классом, см. в моем архиве статей по теме:

http://blogs.msdn.com/b/ericlippert/archive/tags/brittle+base+classes/

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

Почему объявления, объявленные в базовом классе, игнорируются?

И вот еще три актуальных или дублированных вопроса:

С# перегрузка разрешения?

Метод перегружает разрешение и мозговые тизеры Джона Скита

Почему это работает? Перегрузка метода + переопределение метода + полиморфизм

Ответ 3

Я думаю, что это связано с тем, что в случае не виртуального метода используется тип времени компиляции переменной, в которой используется метод.

У вас есть метод Foo, который не является виртуальным и, следовательно, этот метод вызывается.

Эта ссылка имеет очень хорошее объяснение http://msdn.microsoft.com/en-us/library/aa645767%28VS.71%29.aspx

Ответ 4

Итак, вот как это должно работать в соответствии со спецификацией (во время компиляции и при условии, что я правильно перевел документы):

Компилятор идентифицирует список методов сопоставления из типа D и его базовых типов на основе имени метода и списка аргументов. Это означает, что любой метод с именем Foo, принимающий один параметр типа, для которого существует неявное преобразование из B, является допустимым кандидатом. Это приведет к следующему списку:

C.Foo(B) (public virtual)
D.Foo(B) (public override)
D.Foo(A) (public)

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

C.Foo(B) (public virtual)
D.Foo(A) (public)

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

Если N применимо относительно A (Раздел 7.4.2.1), то все методы, объявленные в базовом типе T, удаляются из набора.

Это по существу означает, что если есть применимый метод, объявленный в D, любые методы из базовых классов будут удалены из списка. На этом этапе у нас есть победитель:

D.Foo(A) (public)

Ответ 5

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


public void Foo(A a){
    Console.WriteLine("Foo(A)" + a.GetType().Name);
    Console.WriteLine("Foo(A)" +a.GetType().BaseType );
}

Это предположение, что я не про в .Net