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

Будет ли CLR проверять всю цепочку наследования, чтобы определить, какой виртуальный метод нужно вызвать?

Цепочка наследования следующая:

class A
    {
        public virtual void Foo()
        {
            Console.WriteLine("A method");
        }
    }

class B:A
    {
        public override void Foo()
        {
            Console.WriteLine("B method");
        }
    }

class C:B
    {
        public new virtual void Foo()
        {
            Console.WriteLine("C method");
        }
    }

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

то

class Program
    {
        static void Main(string[] args)
        {
            A tan = new D();
            tan.Foo();
            Console.Read();
        }
    }

В результате вызывается метод foo() в классе B.

Но в reference:

При вызове виртуального метода тип времени выполнения объекта проверяется на переопределение элемента. Превосходящий член в большинстве вызывается класс, который может быть исходным, если нет производный класс переопределил член.

В моей логике CLR сначала обнаруживает, что Foo() является виртуальным методом, он просматривает таблицу методов D, тип среды выполнения, затем обнаруживает, что в этом наиболее производном классе есть переопределяющий член, он должен назовите его и никогда не понимаете, что существует цепочка new Foo() в цепочке наследования.

Что не так с моей логикой?

4b9b3361

Ответ 1

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

Вы начинаете с неправильного места. Ваша переменная имеет тип A и содержит экземпляр D, поэтому используемая виртуальная таблица A 1. Следуя приведенному выше тексту, мы проверяем переопределяющий элемент. Найдем один из B. C не учитывается, потому что он не является переопределяющим, он затеняет базовый метод. А поскольку D переопределяет C, а не A или B, это тоже не считается. Мы ищем переопределяющий элемент в самом производном классе.

Итак, найденный метод B.Foo().

Если вы измените C, чтобы он переопределял вместо теней, найденный метод будет D, потому что он является наиболее производным элементом переопределения.

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

A tan = new B();
tan.Foo();    // which foo is called?  Why, the best Foo of course!  B!

Причина B вызывается потому, что цепочка наследования, которую мы ищем, охватывает от A (тип переменной) до B (тип времени выполнения). C и D больше не являются частью этой цепочки и не являются частью виртуальной таблицы.

Если вместо этого изменить код:

C tan = new D();
tan.Foo();  // which foo, which foo?

Цепочка наследования, которую мы ищем, охватывает от C до D. D имеет переопределяющий элемент, поэтому он вызывается Foo.

Предположим, вы добавили еще один класс Q, который наследует от A и R, который наследуется от Q и т.д. У вас есть две ветки наследования, не так ли? Что выбирается при поиске наиболее производного типа? Следуйте по пути от A (ваш тип переменной) до D (тип времени выполнения).

Надеюсь, это имеет смысл.

1 Не буквально. Виртуальная таблица относится к D, поскольку она является типом среды выполнения и содержит все в цепочке наследования, но ее полезно и легче думать о A как отправной точке. В конце концов, вы ищете производные типы.

Ответ 2

Ответ Эми правильный. Вот как мне нравится смотреть на этот вопрос.

Виртуальный метод - это слот, который может содержать метод.

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

Теперь, имея в виду, рассмотрим ваш пример.

  • A имеет один слот для Foo.
  • B имеет один слот для Foo, унаследованный от A.
  • C имеет два слота для Foo. Один унаследован от B и один новый. Вы сказали, что хотите новый слот с именем Foo, так что вы его получили.
  • D имеет два слота для Foo, унаследованных от C.

Это слоты. Итак, что происходит в этих слотах?

  • В A, A.Foo идет в слот.
  • В B, B.Foo идет в слот.
  • В C, B.Foo идет в первом слоте, а C.Foo идет во второй слот. Помните, что эти слоты совершенно разные. Вы сказали, что хотите два слота с одинаковым именем, так что вы получили. Если это смущает, то ваша проблема. Не делайте этого, если это болит, когда вы это сделаете.
  • В D, B.Foo идет в первом слоте, а D.Foo идет во второй слот.

Итак, что происходит с вашим звонком?

Компилятор объясняет, что вы вызываете Foo на что-то типа времени компиляции A, поэтому он находит первый (и только) слот Foo на A.

Во время выполнения содержимое этого слота B.Foo.

Итак, что называется.

Теперь чувствуете смысл?