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!
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With