有关虚拟/新...加上界面的更多信息!

发布于 2024-08-18 04:30:28 字数 1289 浏览 4 评论 0原文

昨天我发布了一个有关 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 的方法。但如果我们找不到它怎么办?在这些情况下我应该如何处理?为了成功解决这些问题,我是否应该了解任何经验法则?

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

干杯!

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!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

不必在意 2024-08-25 04:30:29

好问题。

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

  • 接口 I1 有一个插槽,我们称之为 I1SLOT。
  • 接口 I1 有一个插槽,我们称之为 I2SLOT。
  • A 类有两个自己的插槽:AMinSLOT 和 ADrawSLOT。
  • A 类具有三个方法,我们将其称为 AMinMethod、ADrawMethod 和 AI2DrawMethod。
  • 当您说“new A”时,运行时有四个槽需要填充。I1SLOT
  • 用ADrawMethod填充。
  • I2SLOT用AI2DrawMethod填充。
  • AMinSLOT 用 AMinMethod 填充。
  • ADrawSLOT由ADrawMethod填充。
  • B 类有 3 个插槽。它继承了AMinSLOT和ADrawSLOT,并定义了一个新的槽BDrawSLOT。
  • B类有两个方法,BDrawMethod和BI1DrawMethod。
  • 当您说“new B”时,运行时有五个个槽需要填充。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 节。

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.

落叶缤纷 2024-08-25 04:30:29

据我了解,您要问的是,给定一个具有一些重写方法的子类,如何知道将调用哪个方法。

基本上,我能想到 3 个选项:

  1. 如果 SubClass 没有在 BaseClass 上定义方法,则 BaseClass 的方法将被调用
  2. 如果 SubClass 覆盖 BaseClass 上的方法,则将调用 SubClass 的方法
  3. 如果 SubClass 定义了一个新方法,该方法也存在于 BaseClass 上,那么调用的方法将取决于对对象的引用(无论您的变量键入为 BaseClass 还是 SubClass)

我希望我理解您的问题

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

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

再浓的妆也掩不了殇 2024-08-25 04:30:29

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

在所有情况下,当引用强制转换为显式实现的接口类型时,都会调用公共 Draw 例外

"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.

肤浅与狂妄 2024-08-25 04:30:29
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< 中的重新实现/code>,它将 IControl.Paint 映射到 MyControl.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() {}
}

这里,DerivedIMethods 的实现将接口方法映射到 Derived.FBase.IMethods.G< /code>、Derived.IMethods.HBase.I结束示例]

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

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() {…}
}

这里,IDerived 的重新实现也重新实现了 IBase,将 IBase.F 映射到 DF >。 结束示例]

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]

岁月蹉跎了容颜 2024-08-25 04:30:29

除了其他答案之外,我还发布了正确答案:

            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

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
归途 2024-08-25 04:30:29

首先解释一下 new 和 virtual

New

  1. new 用于新建一个对象,即创建类的实例或创建类的对象。
  2. new 还用于(字面上覆盖)new 基类中的现有方法。

例如

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. 这表明该方法是虚拟的(即不是永久的或物理的)并且应该被覆盖,即它的定义应该在底层类中提供。

override

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

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.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文