#c# #inheritance #virtual-functions
#c# #наследование #виртуальные функции
Вопрос:
public class Base1
{
public virtual void f()
{
Console.WriteLine("Base1.f()");
}
}
public class Derived1 : Base1
{
// Hides Base1.f() because 'override' was not specified
public new virtual void f()
{
Console.WriteLine("Derived1.f()");
}
}
public class Derived2 : Derived1
{
// Overrides Derived1.f()
public override void f()
{
Console.WriteLine("Derived2.f()");
// Call base method
base.f();
}
}
class Program
{
static void Main(string[] args)
Base1 ob1 = new Derived1();
ob1.f();
Base1 ob2 = new Derived2();
ob2.f();
Derived1 ob3 = new Derived2();
ob3.f();
Derived2 ob4 = new Derived2();
ob4.f();
}
}
// Calls Derived2.f() because Derived2 overrides Derived1().f()
Derived1 ob3 = new Derived2();
ob3.f();
ожидалось, что
Base1 ob2 = new Derived2();
ob2.f();
- Будет вызвана функция derived2, но функция базового класса была
вызывается, в чем причина этого. - Использует ли .net vtables
Комментарии:
1. Пожалуйста, отредактируйте свой вопрос, чтобы отформатировать его соответствующим образом — я бы сделал это сам, но я не могу сказать, что вы действительно пытаетесь сделать в конце.
2. @Jon Пожалуйста, придерживайтесь свадебного Джона, который вы наверняка сможете приобрести в кемпинге!
3. @David: Некоторое время назад наблюдал за свадьбой. Теперь возвращаемся в палатку.
4. @Джон, Насколько я могу судить, это все еще продолжается. На самом деле я путешествовал через Атлантику в США, чтобы избежать этого, но это на каждом телеканале здесь! Американцы просто в восторге от британской королевской свадьбы, вы не поверите.
5. @David: Это то, о чем я серьезно задумывался. Я могу понять, почему британцы могут быть заинтересованы в этом, и я могу понять, почему британско-американца это может волновать. Но я просто не могу понять, почему обычного старого американца волнует британская королевская свадьба. Я, конечно, этого не делаю… Минуту или две я почти волновался, что это может сделать меня плохим человеком, но я справился с этим.
Ответ №1:
Слот метода, используемый статическим анализом во время компиляции, зависит от типа переменной (или выражения), а не от фактического объекта. Переменная ob2
вводится как Base1
, поэтому используется Base1
слот метода. И затем выбирается правильное переопределение на основе введенного (по существу, vtable в этом слоте). Итак, используется базовая функция.
Чтобы использовать функцию derived2, переменная (или выражение) должна быть введена как Derived1
или подкласс.
Ответ №2:
В принципе, если тип переменной, которую вы используете для вызова во время компиляции, f()
равен Base1
, то будет вызван базовый метод — потому что на самом деле его ничто не переопределяет.
Если типом времени компиляции является Derived1
или Derived2
, он вызовет соответствующий метод либо в Derived1
, либо Derived2
на основе типа объекта во время выполнения… потому что в этот момент компилятор будет просто выполнять виртуальный вызов Derived1.f()
, и переопределение произойдет во время выполнения.
И да, .NET использует vtables.
Ответ №3:
Проблема здесь в том, что вы слишком много всего смешиваете.
В принципе, вот что вы сделали:
- Вы определяете виртуальный метод
f
в базовом классе - Вы производите от этого базового класса и создаете новый виртуальный
f
метод - Вы происходите из второго класса и переопределяете
f
, это переопределяет функцию из второго класса, а не из базового класса.
Итак, когда вы говорите:
Base1 b = new Derived2();
b.f();
тогда вы всегда (в данном случае) будете вызывать базовую реализацию f
, поскольку переопределенный f
в Derived2
это другой f
метод. Название то же, но это все еще другой метод.
Причина этого в том, что компилятор увидит, что вызываемая f
вами функция исходит из Base1
класса, и поэтому он вызовет это.
Поскольку ни один класс не переопределяет Base1.f
, это тот, который вы вызываете.
В ответ на вопрос в комментарии, строго говоря, класс будет иметь два виртуальных метода, оба с именами f .
Однако одна из них затеняется новой, введенной в Derived1.
Вы можете внутри класса выбрать, какие вызывать:
public void MethodInDerived1()
{
f(); // calls Derived1.f()
base.f(); // calls Base1.f()
}
Однако извне вам нужно «выбирать» путем приведения.
Другими словами:
Derived1 d = new Derived1();
d.f(); // calls Derived1.f()
((Base1)d).f(); // calls Base1.f()
Вы также можете наблюдать за методами с помощью отражения. Если вы выполните следующий код в LINQPad, вы увидите, что существует два метода, оба названных f
:
void Main()
{
typeof(Derived1).GetMethods().Dump();
}
public class Base1
{
public virtual void f()
{
Debug.WriteLine("Base1.f");
}
}
public class Derived1 : Base1
{
public virtual new void f()
{
Debug.WriteLine("Derived1.f");
}
}
public class Derived2 : Derived1
{
public override void f()
{
Debug.WriteLine("Derived2.f");
base.f();
}
}
Выходные данные этого скрипта усечены (справа есть дополнительная информация):
Комментарии:
1. Ну, строго говоря, их будет 2, но лексически вы можете ссылаться только на одну из них, новую, поскольку у них одинаковое имя. Однако вы можете найти их с помощью отражения. Вы также можете вызвать
base.f
, чтобы явно выбрать базовый метод.2. в vtables как хранится эта информация, потому что у нее есть 2 виртуальные функции