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

时间:2010-01-15 16:10:44

标签: c# interface virtual override 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

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

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

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

干杯!

6 个答案:

答案 0 :(得分:5)

好问题。

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

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

如果您对如何实现它感兴趣,请使用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节。

答案 1 :(得分:4)

根据我的理解,你会问,给定一个带有一些重写方法的子类,如何知道将调用哪个方法。

基本上,我可以想到3种选择:

  1. 如果SubClass没有在BaseClass上定义方法,那么将调用BaseClass的方法
  2. 如果SubClass在BaseClass上覆盖了一个方法,那么将调用SubClass的方法
  3. 如果SubClass定义了一个也存在于BaseClass上的NEW方法,那么被调用的方法将取决于对象的REFERENCE(无论你的变量是否被键入为BaseClass或SubClass)
  4. 我希望我理解你的问题

    编辑关于接口的快速说明:如果BaseClass实现了IInterface,那么派生自BaseClass的SubClass已经具有IInterface的实现,并且不需要重新实现它。例如。如果有一个带有MPH属性的IVehicle,并且有一个实现它的BaseVehicle,因为Car派生自BaseVehicle,Car 已经一个MPH属性

答案 2 :(得分:1)

“方法表”?不,这只是一份必须满足的合同。 I1I2的合同可以使用相同的Draw方法来满足,并且除非您使用隐式实现将其中一个分开,否则就像这里的情况一样。如果没有这个,单个Draw方法就可以了。

在所有情况下,当引用转换为明确实现的接口类型时,公共Draw将被称为,但除外。

答案 3 :(得分:1)

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

注释和参考:我必须回到标准(ECMA-334),这可以在§20.4.4接口重新实现中找到:

  

允许继承接口实现的类通过将其包含在基类列表中来重新实现接口。

     

接口的重新实现遵循与接口的初始实现完全相同的接口映射规则。因此,继承的接口映射对于为接口的重新实现而建立的接口映射没有任何影响。 [示例:在声明中

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

ControlIControl.Paint映射到Control.IControl.Paint并不会影响MyControl中重新实现的事实,IControl.PaintMyControl.Paint映射到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() {} } 结束示例]

     

继承的公共成员声明和继承的显式接口成员声明参与重新实现的接口的接口映射过程。 [实施例

IMethods
  

此处,DerivedDerived.F的实施将界面方法映射到Base.IMethods.GDerived.IMethods.HBase.Iinterface 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() {…} } 结束示例]

     

当一个类实现一个接口时,它隐式地也实现了所有接口的基接口。同样,接口的重新实现也隐含地是所有接口的基接口的重新实现。 [实施例

IDerived
  

此处,IBase的重新实施也会重新实现IBase.F,将D.F映射到{{1}}。 结束示例]

答案 4 :(得分:0)

除了其他答案,我发布了一个正确答案:

            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

答案 5 :(得分:0)

首先解释新的和虚拟的

  1. new用于新建对象,即创建类的实例或创建类的对象。
  2. new也用于(覆盖字面)新建基类中的现有方法。
  3. 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.
    

    虚拟

    1. 这确定该方法是虚拟的(即不是永久的或物理的),应该被覆盖,即它的定义应该在基础类中提供。
    2. 倍率

      1. 用于表示您要覆盖此方法等的关键字。