最近,为我提供了以下代码,以帮助您理解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!
现在,在与提出该难题的其他开发人员进行了长时间的聊天之后,我知道了输出的内容,但是我不会为您带来麻烦。我真正遇到的唯一问题是我们如何获得该输出,代码如何逐步执行,继承什么内容等等。我以为
C
将被返回,因为这似乎是已定义的类。然后我想知道是否会返回B
,因为C继承了B
-但B
也继承了A
(这是我感到困惑的地方!)。题:
谁能解释多态性和继承性在检索最终显示在屏幕上的输出中如何发挥作用?
最佳答案
考虑这一点的正确方法是想象每个类都要求其对象具有一定数量的“插槽”。这些插槽充满了方法。问题“实际上调用了什么方法?”需要您弄清楚两件事:
让我们从考虑插槽开始。有两个插槽。 A的所有实例都必须具有一个插槽,我们将其称为GetNameSlotA。 C的所有实例都必须具有一个插槽,我们将其称为GetNameSlotC。这就是C中声明中的"new"的意思-意思是“我想要一个新插槽”。与B中声明上的“覆盖”相比,这意味着“我不需要新的插槽,我想重新使用GetNameSlotA”。
当然,C继承自A,因此C还必须具有插槽GetNameSlotA。因此,C的实例具有两个插槽-GetNameSlotA和GetNameSlotC。非C的A或B实例具有一个插槽GetNameSlotA。
现在,当您创建一个新的C时,这两个插槽中有什么内容?共有三种方法,分别称为GetNameA,GetNameB和GetNameC。
A的声明为“将GetNameA放入GetNameSlotA”。 A是C的父类(super class),因此A的规则适用于C。
B的声明为“将GetNameB放入GetNameSlotA”。 B是C的父类(super class),因此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#重载解析中,选择一个插槽并生成对该插槽中任何内容的调用。