我最近获得了以下一段代码,以帮助理解OOP - C#中的Polymorphism
和Inheritance
。
// 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
(这是我感到困惑的地方!)。< / p>
有人能解释多态性和继承如何在检索输出中发挥作用,最终显示在屏幕上?
答案 0 :(得分:95)
正确的思考方法是想象每个类都要求其对象具有一定数量的“槽”;那些插槽充满了方法。问题是“实际调用什么方法?”要求你弄清楚两件事:
让我们从考虑插槽开始。有两个插槽。 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#重载分辨率中的选择一个插槽并生成对该插槽中发生的任何事件的调用。
答案 1 :(得分:24)
它应返回“B”,因为B.GetName()
保存在A.GetName()
函数的小虚拟表框中。 C.GetName()
是编译时“覆盖”,它不会覆盖虚拟表,因此您无法通过指向A
的指针来检索它。
答案 2 :(得分:3)
很简单,你只需记住继承树。
在您的代码中,您持有对类型为“A”的类的引用,该类由“C”类型的实例实例化。现在,要解析虚拟'GetName()'方法的确切方法地址,编译器会继续继承层次结构并查找最新的覆盖(请注意,只有'virtual'是覆盖, “新”是完全不同的东西......)。
简而言之,发生了什么。类型'C'中的new关键字只会在类型为'C'的实例上调用它时起作用,然后编译器会完全否定所有可能的继承关系。严格来说,这与多态无关 - 你可以看到,无论你使用'new'关键字掩盖虚拟或非虚方法都没有任何区别...
“C”类中的“新”意味着:如果在此(精确)类型的实例上调用“GetName()”,则忘记所有内容并使用此方法。相反,“虚拟”意味着:上传继承树,直到找到具有此名称的方法,无论调用实例的确切类型是什么。答案 3 :(得分:2)
好的,帖子有点老了,但这是一个很好的问题,也是一个很好的答案,所以我只是想加上我的想法。
考虑以下示例,除了主函数外,它与以前的示例相同:
// 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()
{
Console.Write ( "Type a or c: " );
string input = Console.ReadLine();
A instance = null;
if ( input == "a" ) instance = new A();
else if ( input == "c" ) instance = new C();
Console.WriteLine( instance.GetName() );
}
// No compiling!
现在很明显函数调用在编译时不能绑定到特定函数。但是必须编译某些东西,并且该信息只能取决于引用的类型。因此,使用除C类型之外的任何引用都不可能执行C类的GetName函数。
P.S。也许我应该使用术语方法代替函数,但正如莎士比亚所说:任何其他名称的函数仍然是一个函数:)
答案 4 :(得分:-1)
实际上,我认为它应该显示C,因为new运算符只是隐藏了具有相同名称的所有祖先方法。因此,隐藏A和B的方法,只有C保持可见。
http://msdn.microsoft.com/en-us/library/51y09td4%28VS.71%29.aspx#vclrfnew_newmodifier