views:

119

answers:

7

Hello again

Yesterday I posted a question about the new/virtual/override keywords, and i learned a lot from your answers. But still i remain with some doubts.

In between all the "boxes", i lost touch with what's really happening to the type's method tables. For instance:

interface I1 { void Draw(); }
interface I2 { void Draw(); }

class A : I1, I2
{
    public void Minstance() { Console.WriteLine("A::MInstance"); }
    public virtual void Draw() { Console.WriteLine("A::Draw"); }
    void I2.Draw() { Console.WriteLine("A::I2.Draw"); }
}
class B : A, I1, I2
{
    public new virtual void Draw() { Console.WriteLine("B::Draw"); }
    void I1.Draw() { Console.WriteLine("B::I1.Draw"); }
}

class Test
{

    public static void Main()
    {
        A a = new B();
        a.Draw();
        I1 i1 = new A();
        i1.Draw();
        I2 i2 = new B();
        i2.Draw();
        B b = (B)a;
        b.Draw();
    }

}
}

The question thats asked on this exercise is: Fill in the types' method tables according to the code, and explain the output generated by running the Main().

My answer was: In type A we have 3 methods: MInstance(), Draw()- the A::Draw version - and I2::Draw In type B we have 4 methods: MInstance from A, B::Draw, I1::Draw and I2::Draw

I'm not very confident about my answer, and thats why i'm posting this question. When we implement interfaces, it's created a new slot on the method table for the methods of said interface? shouldnt we be implementing I2::Draw too in class A?

Likewise, when we call a method using an interface variable (like i1.Draw()) i understand we're on dynamic dispatch, and therefore we should look at the type of the object being held by the variable(type A in that case) and search A's method table for a method called specifically I1.Draw. But what if we don't find it? How should i proceed in these cases? Is there any rule of thumb i should know about in order to successfully tackle these issues?

Sorry for being so boring with this question, but i really need to untie this knot on my head ;)

Cheers!

+3  A: 

From what I understand you're asking, given a subclass with some overridden methods, how to know which method will get called.

basically, there are 3 options I can think of:

  1. If SubClass does not define a method on BaseClass, then BaseClass's method will get called
  2. If SubClass OVERRIDES a method on BaseClass, then the SubClass's method will get called
  3. If SubClass defines a NEW method which ALSO exists on BaseClass, then the method to get called will depend on the REFERENCE to the object (whether your variable is typed as BaseClass or SubClass)

I hope I understood your question

edit: a quick note about interfaces: If BaseClass implements IInterface, then SubClass, which derives from BaseClass, already has the implementation of IInterface and does not need to re-implement it. E.g. if there's an IVehicle with an MPH property, and there's a BaseVehicle that implements that, since Car derives from BaseVehicle, Car already has an MPH property

statichippo
+1  A: 

"Method table"? No, it's just a contract that has to be satisfied. The contracts of I1 and I2 can be satisfied with the same Draw method, and will be, unless you separate one off with an implicit implementation, as is the case here. Without this, a single Draw method will be fine.

In all cases, the public Draw will be called except when the reference is cast to the interface type that is explicitly implemented.

David M
A: 

In addition to other answers I am posting a correct answer:

            A a = new B();
            a.Draw(); //A::Draw

            I1 i1 = new A();
            i1.Draw(); //A::Draw

            I2 i2 = new B();
            i2.Draw();// B::Draw

            B b = (B) a;
            b.Draw();// B::Draw
Restuta
and what about A a2 = new B(); a2.Draw();? That would output //A::Draw
statichippo
+1  A: 
interface I1 { void Draw(); }
interface I2 { void Draw(); }

class A : I1, I2
{
    // this is just a method in A
    public void Minstance() { Console.WriteLine("A::MInstance"); }

    // method in A, also implements I1.Draw. May be overridden in
    // derived types.
    public virtual void Draw() { Console.WriteLine("A::Draw"); }

    // implements I2.Draw, accessible on object a of type A via ((I2)a).Draw()
    void I2.Draw() { Console.WriteLine("A::I2.Draw"); }
}
class B : A, I1, I2
{
    // new method in B, does not override A.Draw, so A.Draw is only
    // callable on an object b of type B via ((A)b).Draw(). Types
    // derived from B may override this method, but can't override
    // A.Draw because it's hidden. Also implements I2.Draw (see notes).
    public new virtual void Draw() { Console.WriteLine("B::Draw"); }

    // implements I1.Draw, accessible on object b of type B via ((I1)b).Draw()
    void I1.Draw() { Console.WriteLine("B::I2.Draw"); }
}

Notes and reference: I had to go back to the standard (ECMA-334) for this, and it's found in §20.4.4 Interface Re-implementation:

A class that inherits an interface implementation is permitted to re-implement the interface by including it in the base class list.

A re-implementation of an interface follows exactly the same interface mapping rules as an initial implementation of an interface. Thus, the inherited interface mapping has no effect whatsoever on the interface mapping established for the re-implementation of the interface. [Example: In the declarations

interface IControl
{
    void Paint();
}
class Control: IControl
{
    void IControl.Paint() {…}
}
class MyControl: Control, IControl
{
    public void Paint() {}
}

the fact that Control maps IControl.Paint onto Control.IControl.Paint doesn’t affect the reimplementation in MyControl, which maps IControl.Paint onto MyControl.Paint. end example]

Inherited public member declarations and inherited explicit interface member declarations participate in the interface mapping process for re-implemented interfaces. [Example:

interface IMethods
{
    void F();
    void G();
    void H();
    void I();
}
class Base: IMethods
{
    void IMethods.F() {}
    void IMethods.G() {}
    public void H() {}
    public void I() {}
}
class Derived: Base, IMethods
{
    public void F() {}
    void IMethods.H() {}
}

Here, the implementation of IMethods in Derived maps the interface methods onto Derived.F, Base.IMethods.G, Derived.IMethods.H, and Base.I. end example]

When a class implements an interface, it implicitly also implements all that interface’s base interfaces. Likewise, a re-implementation of an interface is also implicitly a re-implementation of all of the interface’s base interfaces. [Example:

interface IBase
{
    void F();
}
interface IDerived: IBase
{
    void G();
}
class C: IDerived
{
    void IBase.F() {…}
    void IDerived.G() {…}
}
class D: C, IDerived
{
    public void F() {…}
    public void G() {…}
}

Here, the re-implementation of IDerived also re-implements IBase, mapping IBase.F onto D.F. end example]

280Z28
+3  A: 

Good question.

The way to think about this is: interfaces get their own set of slots. A class which implements an interface is required to fill in those slots.

  • Interface I1 has a slot we'll call I1SLOT.
  • Interface I1 has a slot we'll call I2SLOT.
  • Class A has two slots of its own, AMinSLOT and ADrawSLOT.
  • Class A has three methods, which we'll call AMinMethod, ADrawMethod and AI2DrawMethod.
  • When you say "new A", the runtime has four slots to fill in.
  • I1SLOT is filled in with ADrawMethod.
  • I2SLOT is filled in with AI2DrawMethod.
  • AMinSLOT is filled in with AMinMethod.
  • ADrawSLOT is filled in with ADrawMethod.
  • Class B has three slots. It inherits AMinSLOT and ADrawSLOT, and defines a new slot, BDrawSLOT.
  • Class B has two methods, BDrawMethod and BI1DrawMethod.
  • When you say "new B" the runtime has five slots to fill in.
  • I1SLOT is filled in with BI1DrawMethod.
  • I2SLOT is filled in with BDrawMethod.
  • AMinSLOT is filled in with AMinMethod.
  • ADrawSLOT is filled in with ADrawMethod.
  • BDrawSLOT is filled in with BDrawMethod.

Now remember, the job of overload resolution is to choose the slot based on the type and the arguments. There are no arguments, so the compiler only has the type to go off of.

  • When you call Draw on an object of compile-time type A, the best match is ADrawSLOT.
  • When you call Draw on an object of compile-time type B, the best match is BDrawSLOT.
  • When you call Draw on an object of compile-time type I1, the best match is I1SLOT.
  • When you call Draw on an object of compile-time type I2, the best match is I2SLOT.

And the compiler generates code that says "call whatever method is in the chosen slot at runtime."

Summing up:

A a1 = new A();
A a2 = new B();
B b = new B();
(a1 as A).Draw();  // ADrawSLOT contains A::Draw
(a1 as I1).Draw(); // I1SLOT    contains A::Draw
(a1 as I2).Draw(); // I2SLOT    contains A::I2.Draw
(a2 as A).Draw();  // ADrawSLOT contains A::Draw
(a2 as B).Draw();  // BDrawSLOT contains B::Draw
(a2 as I1).Draw(); // I1SLOT    contains B::I1.Draw
(a2 as I2).Draw(); // I2SLOT    contains B::Draw
(b as A).Draw();   // ADrawSLOT contains A::Draw
(b as B).Draw();   // BDrawSLOT contains B::Draw
(b as I1).Draw();  // I1SLOT    contains B::I1Draw
(b as I2).Draw();  // I2SLOT    contains B::Draw

If you're interested in how this is implemented, use ILDASM to disassemble your program, and then look at metadata table 25 (0x19), the MethodImpl table. This program's MethodImplTable is:

1 == 0:TypeDef[2000004], 1:MethodDefOrRef[06000005], 2:MethodDefOrRef[06000002]
2 == 0:TypeDef[2000005], 1:MethodDefOrRef[06000008], 2:MethodDefOrRef[06000001]

Then you can look in the typedef and methoddef tables, and you'll see that this decodes as:

in type A the method A::I2.Draw implements the method I2::Draw
in type B the method B::I1.Draw implements the method I1::Draw

The MethodImpl table is how the CLI represents the notion of "I need to stick something in this slot that is different than what the regular name matching rules would choose". Normally the name matching rules would choose a method called "Draw" to go in that slot, not "I1.Draw" or "I2.Draw".

You might also want to read section 22.27 of Partition II of the CLI spec.

Eric Lippert
What does `((I2)(A)b).Draw()` call? :o
280Z28
It calls B.Draw. It calls whatever is in I2SLOT of the object referred to by B. The compiler chooses slot I2SLOT, and the runtime looks at what is in that slot at runtime. The cast to A is irrelevant.
Eric Lippert
When the compiler says "call whatever method is in the chosen slot at runtime." the slot is chosen based on the type of the variable holding the reference to the object right?
Bruno
The slot is chosen based on the compile-time type of the *value* ; it need not be a variable. When you say Foo().Bar(), the slot is chosen for the call to Bar() is based on the compile-time type of the return of Foo(); there's no *variable* at all in that example.
Eric Lippert
A: 

Firstly the explanation of new and virtual

New

  1. new is used to either new up an object i.e. create an instance of a class or make an object of the class.
  2. new is also used to (override literally) new up the existing method in the base class.

e.g.

A a2= new B() (creates a new object using constructor of B, because b is of type A, therefore it works )
and a2.draw() will result in execution of A class draw because the type is A.

Virtual

  1. This establishes that the method is virtual (i.e. not permanent or physical) and should be overridden, i.e. its definition should be provided int the underlying class.

override

  1. Keyword used to denote that you would like to override this method etc.
A: 

Firstly the explanation of new and virtual

New

  1. new is used to either new up an object i.e. create an instance of a class or make an object of the class.
  2. new is also used to (override literally) new up the existing method in the base class.

e.g.

A a2= new B() (creates a new object using constructor of B, because b is of type A, therefore it works )
and a2.draw() will result in execution of A class draw because the type is A.

Virtual

  1. This establishes that the method is virtual (i.e. not permanent or physical) and should be overridden, i.e. its definition should be provided int the underlying class.

override

  1. Keyword used to denote that you would like to override this method etc.