views:

212

answers:

6

I am a bit confused about the virtual/new/override thing, here's some 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 dont 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? Doesnt have one...but does have a virtual method with that signature and name!

Virtual method? Lets 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, would greatly appreciate some help.

Cheers ;)

A: 

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.

Charles Bretana
key is that in the second case, because of the new, he is calling a non-virtual method...
Charles Bretana
+2  A: 

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.

Kyralessa
Yeah i have warnings, but see, this is an exam question and the questions are made not regarding if the code throws warnings or not...it only has to compile correctly. And the question is: what is the output on the console and why?I understand that in class B we should be doing new void mVVirtual()..., but i guess that if we let it be virtual it will have the same effect (create a new slot on the type's method tables), but of course throwing a warning.
Bruno
In class B you should either use `new` or `override`. The code given doesn't use either one. The compiler *could* raise an error in such a case; but instead it chooses to assume that you meant `new`, and shows a warning. The only effect of `virtual` in B is to let you override that new method in C; it doesn't have any connection to A at all.
Kyralessa
I've used new in class B and the output remains A::mVVirtual...and i still can't see why :S
Bruno
@Bruno: because what you want in this case is to label B::mVVirtual as `virtual override`. When you think about the combined meaning of the modifiers you get the behavior you expect. It's just that because A's implementation was `virtual`, when you declare B's implementation as `override`, it inherits the `virtual` modifier automatically, so there's no need to specify it redundantly.
Daniel Pryden
A: 

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.

Fredou
+5  A: 

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 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 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 {
    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 {
    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.

Jason
Well it makes sense from this point of view...but then the "algorithm" that i was talking about falls apart because i thought that when the compiler sees the method marked as virtual, it searches in the object's type for a overridden version of the method and if it exists, executes it! If it doesnt exist, we climb all the way throug the hierarchy searcing for an override...I dont know if im explaining myself properly...
Bruno
You are explaining yourself properly; I understand the way that you are thinking of it. What my explanation shows is that that way of thinking of it is a little off.
Jason
But for the majority of cases my way of thinking about it is good, right? Only in this case it's a little off.
Bruno
If it were impossible to hide the "box" your thinking would be fine.
Jason
To clarify, It is NOT the instance of a type that has these 'boxes' It is the TYPE itself. Every Type has a "method Table" with pointers to all the methods in that type. Each Types' method table is structured with the virtual methods first, from `Object` (furthest up the inheritance chain) first. to the virtual methods declared in the type itself. After are the non-virtual methods. When the compiler resolves a virtual method call it goes to the method table for the Type of the object itself, whereas for a non-virtual call it goes to the method table for declared type of the variable.
Charles Bretana
+3  A: 

UPDATE : For a more information about this language feature, see the follow-up question here: http://stackoverflow.com/questions/2072983/more-about-virtual-new-plus-interfaces/2073762

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.

Eric Lippert
Got it...Jason's answer really cracked it open for me! Thanks guys ;)
Bruno
A: 

In a similar perspective, which modifications do i have to make so this code would print A Explicit?

class Teste { public static void Main() { A a = new B(); a.MVirtual(); I i1 = a; i1.MVirtual(); } }

interface I
{
   void MVirtual();
}
class A : I
{
    public void MVirtual() { Console.Write("A "); }
    void I.MVirtual() { Console.Write("Explicit "); }
}
class B : A, I
{
    public void MVirtual() { Console.Write("B "); }
}

It is printed A B. But if i want A Explicit to be printed, what do i have to do with the methods in the A and B classes?

Bruno