When you define a virtual method you are allowing that method to be overridden at some point in the inheritance tree. A descendant class such as B
in your example can change the logic of the method in some way. Calls to the virtual method will use the most derived override, dependent on the concrete type of an object instance rather than the type of the variable that holds the instance.
A Virtual Method Table (VMT) is used to track these virtual methods and any overrides that may have been applied. A variable of type A
may hold any class that is derived from class A
- in your example, any of A
, B
, C
or D
. The VMT is used to determine which override method to call when you call a virtual method. So in your example, B
overrides method M
. When you invoke method M
the program looks in the VMT for the correct method to call, and invokes that method.
Each class defines its own VMT, and a variable will use the VMT that relates to its own type. Thus a variable of type A
will use the VMT for type A
, type B
variables will use the VMT for type B
and so on, regardless of the actual (concrete) class of the instance stored in that variable. Override resolution will process the VMTs of the instance to find the correct method to execute.
The new
operator declares a method that has the same name, but is not part of the resolution chain for the method it replaces. The new method may be virtual, in which case it gets its own set of VMT entries, or it might be a standard non-virtual method.
In order to correctly manage virtual methods and overrides, the compiler must have some information to work with. Since it cannot know at compile-time what a variable might hold, it only has the variable's type to go by. So when you call a method on a variable of type A
, it resolves the call as it should be done for type A
- by generating code to do run-time overload resolution on an object of type A. Same goes for variables of any type... the generated code works on the declared type, and resolves overrides at run-time.
So when you declare a variable of type C
, then invoke method M
, it is looking for the method named M
that is defined at or below C
. Since you used the new
modifier, this will never result in back-tracking to the virtual method defined in A
or its overload in B
. Since C.M
is virtual it will use the VMTs to find the right override of your new virtual method.
So in general which method is actually called when a virtual method is invoked is dependent on the concrete type of the instance. The new
keyword makes the type of the variable important as well.
Some of the above is more descriptively accurate than technically accurate. The actual implementation may differ, as always :)