有关Virtual/new ... plus接口的更多信息!

Bru*_*uno 4 c# virtual overriding interface new-operator

昨天我发布了一个关于new/virtual/override关键字的问题,我从你的答案中学到了很多东西.但我仍有一些疑虑.

在所有"盒子"之间,我与类型的方法表中真正发生的事情失去联系.例如:

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();
    }

}
}
Run Code Online (Sandbox Code Playgroud)

关于此练习的问题是:根据代码填写类型的方法表,并解释运行Main()生成的输出.

我的回答是:在A型,我们有3种方法:MInstance(),画() - 在A ::画版 - 和I2 ::绘制B型,我们有4种方法:MInstance从A,B ::抽奖,I1 :: Draw和I2 :: Draw

我对自己的答案不太自信,这就是为什么我发布这个问题.当我们实现接口时,它在方法表上为所述接口的方法创建了一个新槽?我们不应该在A类中实现I2 :: Draw吗?

同样地,当我们称使用接口可变的方法(如i1.Draw())我明白我们是在动态调度,因此,我们应该看看对象的类型被保持由变量(A型在这种情况下)并在A的方法表中搜索专门调用I1.Draw的方法.但是,如果我们找不到它呢?我应该如何处理这些案件?为了成功解决这些问题,我应该知道任何经验法则吗?

很抱歉这个问题太无聊了,但我真的需要解开这个问题;)

干杯!

Eri*_*ert 5

好问题.

考虑这一点的方法是:接口获得自己的一组插槽.需要一个实现接口的类来填充这些插槽.

  • 接口I1有一个我们称之为I1SLOT的插槽.
  • 接口I1有一个我们称之为I2SLOT的插槽.
  • A类有两个自己的插槽,AMinSLOT和ADrawSLOT.
  • A类有三种方法,我们称之为AMinMethod,ADrawMethod和AI2DrawMethod.
  • 当您说"new A"时,运行时有四个要填写的插槽.
  • I1SLOT用ADrawMethod填充.
  • I2SLOT填写了AI2DrawMethod.
  • AMinSLOT填写了AMinMethod.
  • 使用ADrawMethod填充ADrawSLOT.
  • B类有三个插槽.它继承了AMinSLOT和ADrawSLOT,并定义了一个新的插槽BDrawSLOT.
  • B类有两种方法,BDrawMethod和BI1DrawMethod.
  • 当你说"new B"时,运行时有五个插槽可以填写.
  • 使用BI1DrawMethod填充I1SLOT.
  • 用BDrawMethod填写I2SLOT.
  • AMinSLOT填写了AMinMethod.
  • 使用ADrawMethod填充ADrawSLOT.
  • 用BDrawMethod填充BDrawSLOT.

现在请记住,重载解析的工作是根据类型和参数选择插槽.没有参数,所以编译器只有类型可以去掉.

  • 当您在编译时类型A的对象上调用Draw时,最佳匹配是ADrawSLOT.
  • 当您在编译时类型B的对象上调用Draw时,最佳匹配是BDrawSLOT.
  • 当您在编译时类型I1的对象上调用Draw时,最佳匹配是I1SLOT.
  • 当您在编译时类型I2的对象上调用Draw时,最佳匹配是I2SLOT.

编译器生成的代码"在运行时调用所选插槽中的任何方法".

加起来:

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
Run Code Online (Sandbox Code Playgroud)

如果您对如何实现它感兴趣,请使用ILDASM反汇编您的程序,然后查看元数据表25(0x19),MethodImpl表.这个程序的MethodImplTable是:

1 == 0:TypeDef[2000004], 1:MethodDefOrRef[06000005], 2:MethodDefOrRef[06000002]
2 == 0:TypeDef[2000005], 1:MethodDefOrRef[06000008], 2:MethodDefOrRef[06000001]
Run Code Online (Sandbox Code Playgroud)

然后你可以查看typedef和methoddef表,你会看到这个解码为:

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
Run Code Online (Sandbox Code Playgroud)

MethodImpl表是CLI如何表示"我需要在此插槽中粘贴某些内容与常规名称匹配规则选择的内容不同"的概念.通常,名称匹配规则会选择一个名为"Draw"的方法进入该槽,而不是"I1.Draw"或"I2.Draw".

您可能还想阅读CLI规范的第II部分第22.27节.