c# - 更多关于 Virtual/new...plus 界面的信息!

标签 c# interface virtual overriding 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();
    }

}
}

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

我的回答是: 在类型 A 中,我们有 3 个方法:MInstance()、Draw()——A::Draw 版本——和 I2::Draw 在类型 B 中,我们有 4 个方法:来自 A 的 MInstance、B::Draw、I1::Draw 和 I2::Draw

我对我的回答不是很自信,这就是我发布这个问题的原因。当我们实现接口(interface)时,它会在方法表上为所述接口(interface)的方法创建一个新槽吗?我们不应该在 A 类中也实现 I2::Draw 吗?

同样,当我们使用接口(interface)变量(如 i1.Draw())调用方法时,我知道我们正在进行动态调度,因此我们应该查看变量持有的对象类型(类型 A在这种情况下)并在 A 的方法表中搜索专门称为 I1.Draw 的方法。但是如果我们找不到呢?在这些情况下我应该如何处理?为了成功解决这些问题,我应该了解什么经验法则?

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

干杯!

最佳答案

好问题。

思考的方式是:接口(interface)有自己的一组插槽。需要一个实现接口(interface)的类来填充这些槽。

  • I1 接口(interface)有一个插槽,我们称之为 I1SLOT。
  • 接口(interface) I1 有一个插槽,我们称之为 I2SLOT。
  • A 类有两个自己的槽,AMinSLOT 和 ADrawSLOT。
  • A 类有三个方法,我们称之为 AMinMethod、ADrawMethod 和 AI2DrawMethod。
  • 当您说“新 A”时,运行时有四个 槽位需要填充。
  • I1SLOT 由 ADrawMethod 填充。
  • I2SLOT 由 AI2DrawMethod 填充。
  • AMinSLOT 由 AMinMethod 填充。
  • ADrawSLOT 由 ADrawMethod 填充。
  • B 类有三个插槽。它继承了AMinSLOT和ADrawSLOT,并定义了一个新的插槽BDrawSLOT。
  • B 类有两个方法,BDrawMethod 和 BI1DrawMethod。
  • 当您说“新 B”时,运行时有 5 个槽位需要填充。
  • I1SLOT 由 BI1DrawMethod 填充。
  • I2SLOT 用 BDrawMethod 填充。
  • AMinSLOT 由 AMinMethod 填充。
  • ADrawSLOT 由 ADrawMethod 填充。
  • BDrawSLOT 由 BDrawMethod 填充。

现在记住,重载决策的工作是根据类型和参数选择插槽。没有参数,因此编译器只有要关闭的类型。

  • 当您在编译时类型 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

如果您对其实现方式感兴趣,请使用 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]

然后你可以查看 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

MethodImpl 表是 CLI 表示“我需要在这个插槽中插入与常规名称匹配规则选择不同的东西”的概念的方式。通常,名称匹配规则会选择一个名为“Draw”的方法进入该插槽,而不是“I1.Draw”或“I2.Draw”。

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

关于c# - 更多关于 Virtual/new...plus 界面的信息!,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2072983/

相关文章:

c# - 按项目类型对数组进行排序

javascript - MVC Controller 方法不是从 ajax 调用的

c# - 自定义控件是否继承基类 INotifyPropertyChanged 接口(interface)?

c# - Entity Framework - 一对多关系的问题

c# - 未捕获的语法错误 : Unexpected token ILLEGAL ASP. NET mvc3

c# - 校验和中的 "int &= 0xFF"有什么作用?

java - 编写接口(interface)时如何避免重复代码?

c# - 我想我错过了 "Programming to an interface"概念的一些东西

c++ - cpp 文件中的模板特化实现导致模板 ID 不匹配错误

C++ : inheritance without virtuality