I can surely answer to this question by myself writing a dummy test but I want to know what people think about the question. Here it is:
Which method will be call when we have both at the same time overloading and overriding? I am only considering Type overloading and not arity overloading and when Type the overload are related.
Let me throw you an example:
class AA {}
class BB : AA {}
class A {
public virtual void methodA(AA anAA) { Console.Write("A:methodA(AA) called"); }
public virtual void methodA(BB aBB) { Console.Write("A:methodA(BB) called"); }
}
class B : A {
public override void methodA(AA anAA) { Console.Write("B:methodA(AA) called"); }
}
new B().methodA(new BB()); // Case 1
new B().methodA(new AA()); // Case 2
new B().methodA((AA)new BB()); // Case 3
Can you tell what will happen in case 1, 2, and 3?
I personally think that overloadaing is evil and that there is no consistent thinking that could lead to a predictable answer. And that is completely base on a convention implemented in the compiler+vm.
EDIT: If you have some doubt about why overload is evil you can read the blog post from Gilad Brach
Thanks
No, it is entirely predictable. The method signature is resolved first - that is, the overload is determined first. Then, the most overridden method is called. So the output will be:
A:methodA(BB) called
B:methodA(AA) called
B:methodA(AA) called
The method taking an instance of AA will be called in the second two cases, because this is the type of the reference that is passed in, and it is B's version that is called. Note that even this would produce the same result:
A instance = new B();
instance.methodA((AA)new BB()); // Case 3
Overridden methods are excluded from method set when compiler determines which method to call. See member lookup algorithm. So, when you call methodA on type B, set of members with name methodA from type B and it's base type will be constructed:
override B.methodA(AA)
virtual A.methodA(AA)
virtual A.methodA(BB)
Then members with ovveride modifier removed from set:
virtual A.methodA(AA)
virtual A.methodA(BB)
This group of methods is the result of lookup. After that overload resolution applied to define which member to invoke.
A.methodA(BB) is invoked, because its argument matches parameter.
A.methodA(AA) will be chosen, but it is virtual method, so actually call goes to B.method(AA)
Same as option 2
I think the result will be this
case 1 : Console.Write("A:methodA(BB) called");
case 2 : Console.Write("B:methodA(AA) called");
case 3 : Console.Write("B:methodA(AA) called");
in case 3 it will look the type that it's passed, and it's B
Related
Why does this program print "abc from B" instead of "abc from A"? My intuition says that it should resolve the method to the more specific type parameter (which is how I've always used overloading).
public class Program
{
public static void Main(string[] args)
{
int i = 5;
B b = new B();
b.Method1(i);
Console.ReadLine();
}
}
class A
{
public void Method1(int q)
{
Console.WriteLine("abc from A");
}
}
class B : A
{
public void Method1(double p)
{
Console.WriteLine("abc from B");
}
}
Overload resolution doesn't work across types.
This is due to one crucial step in determining the set of candidate methods for overload resolution, as stated in the language spec:
The set of candidate methods is reduced to contain only methods from the most derived types: For each method C.F in the set, where C is the type in which the method F is declared, all methods declared in a base type of C are removed from the set.
Basically, the A.Method1 isn't even considered for overload resolution.
There are a few other steps to this, but the spec nicely summarises the process:
To locate the particular method invoked by a method invocation, start with the type indicated by the method invocation and proceed up the inheritance chain until at least one applicable, accessible, non-override method declaration is found. Then perform type inference and overload resolution on the set of applicable, accessible, non-override methods declared in that type and invoke the method thus selected.
Interestingly, your intuition that A.Method1 should be called because there is an identity conversion from int to int is how it works in Java :)
Because you called it from a variable of the B type. If you cast b as an A, you'd get the first result. See it here:
https://dotnetfiddle.net/TAYfJX
If you want it to do overloading based on argument type, put both versions of the method in the same type.
I can surely answer to this question by myself writing a dummy test but I want to know what people think about the question. Here it is:
Which method will be call when we have both at the same time overloading and overriding? I am only considering Type overloading and not arity overloading and when Type the overload are related.
Let me throw you an example:
class AA {}
class BB : AA {}
class A {
public virtual void methodA(AA anAA) { Console.Write("A:methodA(AA) called"); }
public virtual void methodA(BB aBB) { Console.Write("A:methodA(BB) called"); }
}
class B : A {
public override void methodA(AA anAA) { Console.Write("B:methodA(AA) called"); }
}
new B().methodA(new BB()); // Case 1
new B().methodA(new AA()); // Case 2
new B().methodA((AA)new BB()); // Case 3
Can you tell what will happen in case 1, 2, and 3?
I personally think that overloadaing is evil and that there is no consistent thinking that could lead to a predictable answer. And that is completely base on a convention implemented in the compiler+vm.
EDIT: If you have some doubt about why overload is evil you can read the blog post from Gilad Brach
Thanks
No, it is entirely predictable. The method signature is resolved first - that is, the overload is determined first. Then, the most overridden method is called. So the output will be:
A:methodA(BB) called
B:methodA(AA) called
B:methodA(AA) called
The method taking an instance of AA will be called in the second two cases, because this is the type of the reference that is passed in, and it is B's version that is called. Note that even this would produce the same result:
A instance = new B();
instance.methodA((AA)new BB()); // Case 3
Overridden methods are excluded from method set when compiler determines which method to call. See member lookup algorithm. So, when you call methodA on type B, set of members with name methodA from type B and it's base type will be constructed:
override B.methodA(AA)
virtual A.methodA(AA)
virtual A.methodA(BB)
Then members with ovveride modifier removed from set:
virtual A.methodA(AA)
virtual A.methodA(BB)
This group of methods is the result of lookup. After that overload resolution applied to define which member to invoke.
A.methodA(BB) is invoked, because its argument matches parameter.
A.methodA(AA) will be chosen, but it is virtual method, so actually call goes to B.method(AA)
Same as option 2
I think the result will be this
case 1 : Console.Write("A:methodA(BB) called");
case 2 : Console.Write("B:methodA(AA) called");
case 3 : Console.Write("B:methodA(AA) called");
in case 3 it will look the type that it's passed, and it's B
I wonder about the following behavior:
public class A {
public virtual void Do() { Console.WriteLine("A"); }
}
public class B : A {
public override void Do() { Console.WriteLine("B override"); }
public void Do(int value = 0) { Console.WriteLine("B overload"); }
}
class Program {
public static void Main() {
new B().Do(); // ---> Console: "B overload"
}
}
I expect that an overload with the exact signature has precedence over another overload with optional parameters: I expect "B override" in the console. Instead the program writes "B overload" into the console.
Even resharper fails and falls into the trap:
... Resharper says that the overload with the optional parameter is hidden by the overload with the exact signature, but in fact it is the contrary.
Now, if you remove the inheritance, then it behaves as expected and resharper warning is legitimate:
public class B {
public virtual void Do() { Console.WriteLine("B override"); }
public void Do(int value = 0) { Console.WriteLine("B overload"); }
}
class Program {
public static void Main() {
new B().Do(); // ---> Console: "B override"
}
}
So the question: What is the precedence rule that explains this observation? Why an overload with exact signature doesn't have precedence over another overload with optional parameters in case that the overload with exact parameters overrides a base implementation?
Why an overload with exact signature doesn't have precedence over another overload with optional parameters in case that the overload with exact parameters overrides a base implementation?
Basically this is the compiler following the rules of the specification, even though they're surprising in this case. (Section 7.6.5.1 is the relevant part of the C# 5 spec.)
The compiler looks at the "deepest" type first - i.e. the one with the compile-time type of the target (B in this case) and tries to find an applicable function member ignoring any methods which override those declared in a base class:
The set of candidate methods is reduced to contain only methods from the most derived types: For each method C.F in the set, where C is the type in which the method F is declared, all methods declared in a base type of C are removed from the set.
and:
The intuitive effect of the resolution rules described above is as follows: To locate the particular method invoked by a method invocation, start with the type indicated by the method invocation and proceed up the inheritance chain until at least one applicable, accessible, non-override method declaration is found. Then perform type inference and overload resolution on the set of applicable, accessible, non-override methods declared in that type and invoke the method thus selected. If no method was found, try instead to process the invocation as an extension method invocation.
So in your case, the compiler only considers the newly-introduced method, finds it's applicable (using the default parameter value) and stops its search. (i.e. it doesn't look at methods declared in base classes). At that point, the set of candidate function members only has one entry, so there's no real overload resolution to perform at that point.
I have an article on overloading which shows this sort of thing, not using an optional parameter but using a different parameter type - see the "Inheritance" section.
My fret is: In the code presented below, it should display A then B. But it displays B then B. Why is it so?
What I feel is, constructor of A gets executed first when creating object of B. In that case, the method in B would not be hitted right? So it should be A.Display() and should result A. Also, then a.Display() should return B because we have override.
So I expect A then B. Because its not overloading but overriding. I know the definition of these things, I expect to understand the reason for this behavior and how it works internally as I am not convinced with BB but AB.
Code
class A
{
public A()
{
this.Display();
}
public virtual void Display()
{
Console.WriteLine("A");
}
}
class B :A
{
public override void Display()
{
Console.WriteLine("B");
}
}
class C
{
static void Main()
{
A a = new B();
a.Display();
Console.WriteLine();
Console.ReadLine();
}
}
Outputs
1) On override of Display method in the derived class yields the following:
A a = new A(); // ---> AA
B a = new B(); // ---> BB // I expect AB.
A a = new B(); // ---> BB // I expect AB.
2) using the NEW keyword in the Display method in derived class yields the following:
B a = new B(); // ---> AB // I Expect AA here.
A a = new B(); // ---> AA
A a = new A(); // ---> AA
3) More interesting findings are:
When I use base.Display() in the derived constructor with override of the base method in derived class, it gives me BAB
I do not see any logic at least in this. because, it should give BBB
What I feel is, constructor of A gets executed first when creating object of B.
Correct.
In that case, the method in B would not be hit right?
This is incorrect.
In similar code in C++ you would be correct. In C++ there is a rule that virtual function dispatch tables are built as the object is being constructed. That is, when the "A" constructor is entered, the vtable is filled in with the methods in "A". When control goes to the "B" ctor, the vtable is then filled in with the methods of B.
This is not the case in C#. In C# the vtable is filled in the moment the object comes out of the memory allocator, before either ctor is executed, and it does not change after that. The vtable slot for the method always contains the most derived method.
Therefore calling a virtual method in a ctor as you are doing here is a very bad idea. A virtual method can be called where the implementation is on a class whose ctor has not run yet! It might therefore depend on state that has not yet been initialized.
Note that field initializers run before all ctor bodies, so fortunately an override on a more derived class will always run after the field initializers of the overriding class.
The moral of the story is: simply don't do that. Don't ever call a virtual method in a ctor. In C++ you might get a different method than you expect, and in C# you might get a method that uses state that is not initialized. Avoid, avoid, avoid.
Why we shouldn't call virtual methods inside ctor? Is it because we get only the (latest derived) results only) always in the vtable?
Yes. Let me illustrate with an example:
class Bravo
{
public virtual void M()
{
Console.WriteLine("Bravo!");
}
public Bravo()
{
M(); // Dangerous!
}
}
class Delta : Bravo:
{
DateTime creation;
public override void M()
{
Console.WriteLine(creation);
}
public Delta()
{
creation = DateTime.Now;
}
}
OK, so the expected behavior of this program is that when M is called on any Delta, it will print out the time that the instance was created. But the order of events on new Delta() is:
Bravo ctor runs
Bravo ctor calls this.M
M is virtual and this is of runtime type Delta so Delta.M runs
Delta.M prints out the uninitialized field, which is set to the default time, not the current time.
M returns
Bravo ctor returns
Delta ctor sets the field
Now do you see what I mean when I say that the overriding method might rely on state that is not initialized yet? In any other usage of M, this would be fine because the Delta ctor would already be finished. But here M is called before the Delta ctor even starts!
You create an instance of an object B. It uses the code of the constructor that is defined on class A as you did not override it in B. But the instance is still B, so other methods called in the constructor are the ones defined in B, not A. Hence you see the result of Display() defined in class B.
Update based on update of the question
I'll try to explain the "weird" results you're getting.
When overriding:
B a = new B(); // ---> BB // I expect AB.
A a = new B(); // ---> BB // I expect AB.
This is covered above. When you override a method on a child class, this method is used if you're using an instance of the child class. This is a basic rule that the methods used are decided by the class of the instance of a variable, not by the class used to declare the variable.
When using new modifier for the method (hiding inherited method)
B a = new B(); // ---> AB // I Expect AA here.
Now there's two different behaviours here:
When the constructor is used, it's using the constructor in class A. As the inherited method is hidden in the child class, the constructor is using Display() method from class A and hence you see A printed.
When you later call Display() directly, the instance of the variable is B. For that reason, it uses the method defined on class B which prints B.
Initial statement
I'll start with the base code, I have adapted it to run in LINQPad (I did also change it to Write instead of WriteLine because I'll not preserve the new lines in the explanation anyway).
class A
{
public A()
{
this.Display();
}
public virtual void Display()
{
Console.Write("A"); //changed to Write
}
}
class B :A
{
public override void Display()
{
Console.Write("B"); //changed to Write
}
}
static void Main()
{
A a = new B();
a.Display();
}
The output is:
BB
In your initial question you said you were expecting:
AB
Whats happening here (as Szymon attempted to explain) is that you are creating an object of type B and the class B overrides the method Display of the class A. So whenever you call Display on that object it will be the method of the derived class (B), even from the constructor of A.
I will go over all the cases you mention. I want to encourage to read it carefully. Also, be open minded because this does not match what happens in certain other languages.
1) On override of Display method in the derived class
This is the case where you are overriding the method, ie:
public override void Display()
{
Console.Write("B"); //changed to Write
}
When you override, for all practical uses the method that will be used is the method of the derived class. Think of override as replace.
Case 1:
A a = new A(); // ---> AA
We are ok, with that.
Case 2:
B a = new B(); // ---> BB // I expect AB.
As mentioned above, calling Display on the object will always be the method on the derived class. So, both calls to Display yield B.
Case 3:
A a = new B(); // ---> BB // I expect AB.
This is a variant of the same confusion. The object is clearly of type B, even if you have it in a variable of type A. Remember that in C# the type is a property of the of the object not of the variable. So, the result is the same as above.
Note: You can still use base.Display() to access the method that was replaced.
2) Using the NEW keyword in the Display method in derived class
This is the case where you are hiding the method, ie:
public new void Display()
{
Console.Write("B"); //changed to Write
}
When you hide the method, it means that the original method is still available. You can think of it as a different method (that happens to have the same name and signature). That is: the derived class is not replacing overriding that method.
Because of that, when you do a (virtual) call to the object where at compile time it was decided it was going to use the method of the base class... the method of the derived class is not taken into consideration (in practice, it acts as it weren't a virtual call).
Think of it like this: if you call the method using a varible of the base class... the code is not aware that there exists a derived class that hides the method and that that particular call may be executed with one of those object. Instead, it will use the method of the base class, regardless.
Case 1:
B a = new B(); // ---> AB // I Expect AA here.
You see, at compile time the call in the constructor was set to use the method of the base class. That one gives A. But since the variable is of type B the compiler is aware that the method was hidden for the second call.
Case 2:
A a = new B(); // ---> AA
Here, neither in the constructor nor in the second call it will use the new method. It is not aware of it.
Case 3:
A a = new A(); // ---> AA
And I think this one is clear.
3) Using base.Display()
This the variant of the code where you do this:
public new void Display()
{
base.Display();
Console.Write("B"); //changed to Write
}
base.Display() is gonna be the method in the base class (A), no matter what.
Further reading
You said you want to learn how this works internally.
You can go deeper by reading Microsoft's C# Spec on Virtual Methods
Then read Eric Lippert's Implementing the virtual method pattern in C# (part 1, part 2 and part 3)
You may also be interested:
Anders on virtual calls from constructors.
Eric Lippert's Why Do Initializers Run In The Opposite Order As Constructors? (part 1 and part 2)
Others explanations of Virtual Methods from the web:
What's the difference between override and new? (msdn.com)
C# Virtual (dotnetpearls.com)
Understanding virtual, override and new keyword in C# (dotnet-tricks.com)
Understanding C#: Using virtual and override (oreilly.com)
You may be confusing yourself by naming the class a while instantiating it as class B. If you are looking to call the virtual method you could use the base keyword. The following code writes A B
class A
{
public A()
{
//this.Display();
}
public virtual void Display()
{
Console.WriteLine("A");
}
}
class B : A
{
public override void Display()
{
base.Display();
Console.WriteLine("B");
}
}
class C
{
static void Main(string[] args)
{
A a = new B();
a.Display();
Console.WriteLine();
Console.ReadLine();
}
}
Also note you can see why your code is displaying B B by setting a breakpoint at the beginning and then walking through your code execution line-by-line (F11).
What I understood is , in case of virtual method, same method slot is shared among the parent and child object.
If so, then I think when an object virtual method is called , by somehow compiler updates the method slot with appropriate method address so that exact method is jitted and executed In c#.
I am a bit confused about the virtual/new/override thing. Here's an example:
class A
{
public virtual void mVVirtual() { Console.WriteLine("A::mVVirtual"); }
}
class B : A
{
public virtual void mVVirtual() { Console.WriteLine("B::mVVirtual"); }
}
class C : B
{
public override void mVVirtual() { Console.WriteLine("C::mVVirtual"); }
}
class Test
{
static void Main()
{
B b1 = new C();
b1.mVVirtual(); //C::mVVirtual ... I understand this
A a2 = new C();
a2.mVVirtual(); //A::mVVirtual ... ???
}
}
I don't get why in the second call we get A::mVVirtual. I usually treat these issues with this "algorithm":
Check the type of the variable holding the reference for the object for an instance method called mVVirtual? Doesn't have one...but does have a virtual method with that signature and name!
Virtual method? Let's then check the type of the object being held by a2 (C) for an overriding of that method. It has one -> Executes C::mVVirtual!
Where is my "algorithm" wrong? I really am confused by this, and would greatly appreciate some help.
Here's how you think of virtual methods. Every instance of a class has "boxes" to hold methods. When you mark a method as virtual it says make a new "box" and put a method in it. When you mark a method as override in a derived class, it keeps the "box" from the base class but puts a new method in it.
So here you have a class A and a method named mVVirtual that is marked as virtual. This says make a new "box" named mVVirtual and put a method in it with definition
Console.WriteLine("A::mVVirtual");
Then you have a derived class B and a method named mVVirtual that is marked as virtual. This says make a new "box" named mVVirtual and put a method in it with definition
Console.WriteLine("B::mVVirtual");
In particular, the "box" inherited from A is hidden! It can not be seen by objects that are typed as Bs or classes that derive from B.
Then you have a derived class C and a method named mVVirtual that is marked as override. This says take the "box" named mVVirtual inherited from B and put a different method in it with definition
Console.WriteLine("C::mVVirtual");
Now, when you have
B b1 = new C();
b1.mVVirtual();
you are telling the compiler that b1 is a B so that b1.mVVirtual() looks in the "box" mVVirtual and finds the method with definition
Console.WriteLine("C::mVVirtual");
because b1 is really a C and that is what is in the "box" mVVirtual for instances of C.
But when you have
A a2 = new C();
a2.mVVirtual();
you are telling the compiler that a2 is an A and so it looks in the "box" and finds
Console.WriteLine("A::mVVirtual");
The compiler can not know that a2 is really a C (you've typed it as an A) so it does not know that a2 is really an instance of a class that is derived from a class that has hidden the "box" mVVirtual defined by A. What it does know is that A has a "box" named mVVirtual and so it emits code to invoke the method in that "box".
So, to try to put this succinctly:
class A {
public virtual void mVVirtual() { Console.WriteLine("A::mVVirtual"); }
}
defines a class that has a "box" with full name A::mVVirtual but that you can refer to by the name mVVirtual.
class B : A
{
// "new" method; compiler will tell you that this should be marked "new" for clarity.
public virtual void mVVirtual() { Console.WriteLine("B::mVVirtual"); }
}
defines a class that has a "box" with full name B::mVVirtual but that you can refer to by the name mVVirtual. Referring to B.mVVirtual will not refer to the "box" with full name A::mVVirtual; that "box" can not be seen by objects that are typed as Bs (or classes that derive from B).
class C : B
{
public override void mVVirtual() { Console.WriteLine("C::mVVirtual"); }
}
defines a class that takes the "box" with full name B::mVVirtual and puts a different method in it.
Then
A a2 = new C();
a2.mVVirtual();
says that a2 is an A so that a2.mVVirtual looks in the "box" with full name A::mVVirtual and invokes the method in that "box". This is why you see
A::mVVirtual
on the console.
There are two other method annotaters. abstract makes a new "box" does not put a method definition in the "box". new makes a new "box" and puts a method definition in the "box" but does not allow derived classes to put their own definitions of the method in the "box" (use virtual if you want to do that).
Sorry for being long-winded but I hope that helps.
UPDATE : For a more information about this language feature, see the follow-up question here: More about Virtual / new...plus interfaces!
Jason's answer is correct. To sum it up a bit more succinctly.
You have three methods. Call them MA, MB and MC.
You have two "boxes", or, as they're usually called, slots. We'll stick with Jason's nomenclature. Call them BOX1 and BOX2.
"A" defines BOX1.
"B" defines BOX2.
"C" defines no box; it reuses BOX2.
When you say "new A()", BOX1 is filled in with MA.
When you say "new B()", BOX1 is filled in with MA and BOX2 is filled in with MB.
When you say "new C()", BOX1 is filled in with MA and BOX2 is filled in with MC.
Now suppose you have a variable of type A, and a call to the method. Reason like the compiler. The compiler says "are there any boxes on type A that match this name?" Yes, there is one: BOX1. Therefore, the compiler generates a call to the contents of BOX1.
As we've seen, the contents of BOX1 is always MA, so MA is always called no matter whether the variable is actually holding a reference to A, B, or C.
Now suppose you have a variable of type B, and a call to the method. Again, think like the compiler. The compiler says "are there any boxes on type B that matches this name?" Yes, there are TWO boxes that match by name. The compiler says "which of those two is more closely associated with B?" The answer is BOX2, because B declares BOX2. Therefore, the compiler generates a call to BOX2.
This will call MB if the variable contains a B, because in a B, BOX2 contains MB. This will call MC if the variable contains a C, because in a C, BOX2 contains MC.
Is that now clear? Remember, overload resolution just chooses the box. What the contents of the box are depend upon the object at runtime.
Do you have warnings hidden? When I do what you've done, I get this warning:
'ProjectName.ClassName.B.mVVirtual()' hides inherited member 'ProjectName.ClassName.A.mVVirtual()'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword.
If you used override in class B, you wouldn't have this problem; both cases would give you "C::mVVirtual". Since you're not using override in class B, there's an implicit new in front of the method. This breaks the inheritance chain. Your code is calling a method on type A, and there are no inheriting classes that override that method due to the implicit new. So it has to call class A's implementation.
Best way to think of it is that virtual methods use the actual (or concrete) type of the object to decide what implementation to execute, where non-Virtual methods use the 'declared type of the variabe you are using to access the method to decide which to run...
Override means you are writing a method that is going to 'replace' the implementation for a virtual or abstract method (with the same name/signature) higher up the inheritance chain.
new is used when there is a non-virtual method up the chain with the same name/signature, which the method you are adding will replace...
The difference is as follows
class base { public virtual void foo() { Console.write("base.foo"); } }
class derived { public override void foo() { Console.write("derived.foo"); } }
base b = new base();
b.foo() // prints "base.foo" // no issue b is a base and variable is a base
base b = new derived();
b.foo(); // prints "derived.foo" because concrete tyoe is derived, not base
but
class base { public void foo() { Console.write("base.foo"); } }
class derived { public new void foo() { Console.write("derived.foo"); } }
base b = new base();
b.foo() // prints "base.foo" // no issue b is a base and variable is a base
base b = new derived();
b.foo(); // prints "base.foo" because variable b is base.
derived d = b as derived;
d.foo() // prints "derived.foo" - now variable d is declared as derived.
Your second call prints A::mvVirtual because that method (mVVirtual) is actually not virtual, (in spite of it's name), because it has the new specifier... So it decides based on the variable type, which is A.
To explain what is going on technically, Every Type has a "method Table" with pointers to all the methods in that type. (It is NOT the instance of a type that has this table, It is the TYPE itself.) Each Types' method table is structured with all acessible virtual methods first, from object (furthest up the inheritance chain) at the beginning, to the virtual methods declared in the type itself, at the end. Then, after all the virtual methods are represented, all the non-virtual methods are added, again, from any non-virtual methods in object, first, all the way to any non-virtual methods in the Type itself. The table is structured this way so that the offsets for all virtual metods will be identical in all derived classes' method tables, since the compiler may call these methods from variables declared as other types, even from code in other methods declared and implemented in base types of the concrete class.
When the compiler resolves a virtual method call it goes to the method table for the Type of the object itself, (the concrete type), whereas for a non-virtual call it goes to the method table for declared type of the variable. So if you call a virtual method, even from code in a base type, if the actual concrete type is Type derived from this base type, the compiler goes to the method table for that concrete type.
If you call a non-virtual method, (no matter how far down the inheritance change the actual object's type might be), The compiler access the method table for the variab;es' declared type. This table has nothing in from any derived types furthur down the chain.
This is how I understand it
A is the base class
B inherit A but doesn't override it
C inherit B but does override it
Since you are declaring A but initialize C, it will ignore the override because the base class is A and A never get overridden from B.