Non-virtual methods are resolved according to the type of the expression at compile time, and the call destination is fixed at compile time. The compiler looks up the method name in the symbol info available at compile time in the source code context of the call, and emits a call instruction to call the non-virtual method that it was able to find. It doesn't matter what the actual type of the instance is, the call always goes to the non-virtual method figured out at compile time from static type info.
Here is why the call to Accelerate
goes to Sedan.Accelerate
in the context of Main, but goes to VehicleBase.Accelerate
in the context of the VehicleBase.Drive
:
In the body of your Main
function, you have declared a variable of type Sedan
, and you make the method call using that variable. The compiler looks for the method named "Accelerate" in the type of the variable used to make the call, type Sedan, and finds Sedan.Accelerate
.
Inside the method VehicleBase.Drive
, the compile-time type of "self" is VehicleBase
. This is the only type the compiler can see in this source code context, so that call to Accelerate in 'VehicleBase.Drive' will always go to VehicleBase.Accelerate
, even if the runtime object instance's type is actually Sedan
.
In the body of a method declared in the Sedan
type, the compiler would resolve the non-virtual method call by looking in the methods of the Sedan
type first, then looking at the VehicleBase
type if no match were found in Sedan
.
If you want the call destination to change based on the actual object instance type, you must use virtual methods. Using virtual methods will also give much more consistent execution results, since the call will always go to the most specific implementation determined by the object instance at runtime.
Non-virtual method call destinations are selected according to the compile-time type, with no awareness of runtime. Virtual method call destinations are selected according to the instance type at runtime.