为什么这个多态C#代码会打印它的作用?

Dan*_*May 68 .net c# oop polymorphism inheritance

我最近得到了以下代码作为一种帮助理解PolymorphismInheritance在OOP - C#中的谜题.

// No compiling!
public class A
{
     public virtual string GetName()
     {
          return "A";
     }
 }

 public class B:A
 {
     public override string GetName()
     {
         return "B";
     }
 }

 public class C:B
 {
     public new string GetName()
     {
         return "C";
     }
 }

 void Main()
 {
     A instance = new C();
     Console.WriteLine(instance.GetName());
 }
 // No compiling!
Run Code Online (Sandbox Code Playgroud)

现在,在与提出拼图的其他开发者进行了长时间的长时间聊天后,我知道输出是什么,但我不会为你破坏它.我真正遇到的唯一问题是我们如何得到输出,代码如何逐步通过,继承什么等等.

我认为C会返回,因为它似乎是定义的类.然后我通过我的脑袋了解是否B会因为C继承而返回B- 但B也继承A(这是我困惑的地方!).


题:

任何人都可以解释多态性和继承如何在检索输出中发挥作用,最终显示在屏幕上?

Eri*_*ert 97

考虑这一点的正确方法是想象每个类都要求其对象具有一定数量的"槽"; 那些插槽充满了方法.问题是"实际调用什么方法?" 要求你弄清楚两件事:

  1. 每个插槽的内容是什么?
  2. 叫哪个插槽?

让我们从考虑插槽开始.有两个插槽.A的所有实例都需要有一个我们称之为GetNameSlotA的插槽.C的所有实例都需要有一个我们称之为GetNameSlotC的插槽.这就是"新"在C语言中的含义 - 它意味着"我想要一个新的插槽".与B中声明的"覆盖"相比,这意味着"我不想要新的插槽,我想重新使用GetNameSlotA".

当然,C继承自A,因此C还必须有一个插槽GetNameSlotA.因此,C的实例有两个插槽 - GetNameSlotA和GetNameSlotC.非C的A或B实例有一个插槽GetNameSlotA.

现在,当你创建一个新的C时,这两个插槽是什么?有三种方法,我们称之为GetNameA,GetNameB和GetNameC.

A的声明说"把GetNameA放在GetNameSlotA中".A是C的超类,因此A的规则适用于C.

B的声明说"把GetNameB放在GetNameSlotA中".B是C的超类,因此B的规则适用于C的实例.现在我们在A和B之间存在冲突.B是更多派生类型,因此它胜出--B的规则优先于 A的规则.因此声明中的"覆盖"一词.

C的声明说"把GetNameC放在GetNameSlotC中".

因此,您的新C将有两个插槽.GetNameSlotA将包含GetNameB,GetNameSlotC将包含GetNameC.

我们现在已经确定了什么方法在哪些插槽中,所以我们已经回答了我们的第一个问题.

现在我们必须回答第二个问题.叫什么插槽?

想想它就像你是编译器一样.你有一个变量.所有你知道的是它是A类型.你被要求解决对该变量的方法调用.您查看A上可用的插槽,您可以找到匹配的唯一插槽是GetNameSlotA.你不了解GetNameSlotC,因为你只有一个A类型的变量; 你为什么要寻找只适用于C的插槽?

因此,这是对GetNameSlotA中的任何内容的调用.我们已经确定在运行时,GetNameB将位于该槽中.因此,这是对GetNameB的调用.

这里的关键点是,在C#中,重载决策选择一个插槽并生成对该插槽中发生的任何事件的调用.


Bli*_*ndy 24

它应返回"B",因为B.GetName()它保存在A.GetName()函数的小虚拟表框中.C.GetName()是一个编译时"覆盖",它不会覆盖虚拟表,因此您无法通过指针检索它A.

  • 我认为对`C.GetName()`使用短语"compile time'impall'"有点误导,因为它没有覆盖任何东西.几乎任何其他单词都会更有用:) (10认同)
  • 只有一个vtable,但是`new`方法只是一个碰巧具有相同名称的不同方法.编译器根据接收器的静态类型选择要使用的vtable插槽.`((C)foo).GetName()`总是调用`C.GetName`或者覆盖`C.GetName`如果它是虚拟的,`((A)foo).GetName()`总是会调用`A.GetName`或其中一个覆盖.这里`C.GetName`及其覆盖不是'A.GetName`的覆盖,它们是不同的方法. (5认同)