Разрешение не виртуального метода - почему это происходит
Мое понимание (в C#) того, как разрешаются не виртуальные методы, заключается в том, что это зависит от типа переменной (а не от типа экземпляра).
Посмотрите на код ниже.
class Program
{
static void Main(string[] args)
{
Sedan vehicle = new Sedan();
vehicle.Drive();
vehicle.Accelerate();
}
}
abstract class VehicleBase
{
public void Drive()
{
ShiftIntoGear();
Accelerate();
Steer();
}
protected abstract void ShiftIntoGear();
protected abstract void Steer();
public void Accelerate()
{
Console.WriteLine("VehicleBase.Accelerate");
}
}
class Sedan : VehicleBase
{
protected override void ShiftIntoGear()
{
Console.WriteLine("Sedan.ShiftIntoGear");
}
protected override void Steer()
{
Console.WriteLine("Sedan.Steer");
}
public new void Accelerate()
{
Console.WriteLine("Sedan.Accelerate");
}
}
Консоль Windows показывает следующее:
Sedan.ShiftIntoGear
VehicleBase.Accelerate
Sedan.Steer
Sedan.Accelerate
Это не имеет смысла для меня, и я верю, что многие бросили бы петлю. Если вы теперь объявите переменную Vehicle типа VehicleBase, вы получите
Sedan.ShiftIntoGear
VehicleBase.Accelerate
Sedan.Steer
VehicleBase.Accelerate
Что я и ожидал в предыдущем случае, потому что метод Accelerate не является виртуальным.
В предыдущем выводе (с переменным транспортным средством, типизированным как Седан, я ожидал, что Sedan.Accelerate будет вызываться вместо VehicleBase.Accelerate. В его нынешнем виде, в зависимости от того, откуда вы его вызываете (из класса или со стороны) поведение меняется.
Мне кажется, что правило (ы) разрешения перегрузки для вновь представленных методов имеет приоритет, но мне трудно поверить, что это правильное / ожидаемое поведение.
2 ответа
Невиртуальные методы разрешаются в соответствии с типом выражения во время компиляции, а назначение вызова фиксируется во время компиляции. Компилятор ищет имя метода в информации о символах, доступной во время компиляции в контексте исходного кода вызова, и выдает инструкцию вызова для вызова не виртуального метода, который ему удалось найти. Неважно, что является фактическим типом экземпляра, вызов всегда переходит к не виртуальному методу, вычисленному во время компиляции из статической информации о типе.
Вот почему призыв к Accelerate
идет к Sedan.Accelerate
в контексте Main, но переходит к VehicleBase.Accelerate
в контексте VehicleBase.Drive
:
В теле твоего Main
функция, вы объявили переменную типа Sedan
и вы делаете вызов метода, используя эту переменную. Компилятор ищет метод с именем "Accelerate" в типе переменной, используемой для вызова, типа Sedan, и находит Sedan.Accelerate
,
Внутри метода VehicleBase.Drive
тип "self" во время компиляции VehicleBase
, Это единственный тип, который компилятор может видеть в этом контексте исходного кода, поэтому вызов Accelerate в VehicleBase.Drive всегда будет VehicleBase.Accelerate
даже если тип экземпляра объекта среды выполнения на самом деле Sedan
,
В теле метода, объявленного в Sedan
типа, компилятор будет разрешать вызов не виртуального метода, просматривая методы Sedan
сначала напечатайте, а затем посмотрите на VehicleBase
введите, если не найдено совпадений в Sedan
,
Если вы хотите, чтобы назначение вызова изменялось в зависимости от фактического типа экземпляра объекта, вы должны использовать виртуальные методы. Использование виртуальных методов также даст гораздо более согласованные результаты выполнения, поскольку вызов всегда будет идти к наиболее конкретной реализации, определенной экземпляром объекта во время выполнения.
Назначения для вызова не виртуальных методов выбираются в соответствии с типом времени компиляции без учета времени выполнения. Назначения вызова виртуального метода выбираются в соответствии с типом экземпляра во время выполнения.
Все это имеет смысл - когда вы объявляете автомобиль Sedan
, два вызова Accelerate
разрешаются по-разному:
- Когда звонок
Accelerate
сделан изDrive
метод, он понятия не имеет, что существуетnew
метод вSedan
, так что делает вызов соответствующего метода базы - Когда звонок
Accelerate
сделан изMain
метод, компилятор знает, что вы вызываетеnew
метод, потому что он знает, что точный типvehicle
переменнаяSedan
,
С другой стороны, когда звонок Accelerate
сделан из Main
метод, но переменная объявлена как VehicleBase
компилятор не может предположить, что тип Sedan
так что это решает Accelerate
снова к методу базового класса.