为什么这个多态的C#代码打印它所做的?

最近,我得到了下面这段代码作为帮助理解OOP – C#中的PolymorphismInheritance的一种难题。

 // 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! 

现在,经过与其他开发人员长时间的聊天,我知道输出是什么,但我不会为你破坏它。 我真正唯一的问题是我们如何得到这个输出,代码如何通过,什么是inheritance什么,等等。

我认为C会被返回,因为这似乎是定义的类。 然后,我经历了我的头, B是否会因为CinheritanceB而返回 – 但是B也inheritance了A (这是我弄糊涂的地方!)。


题:

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

想想这个的正确方法是想象每个class级都要求其对象具有一定数量的“插槽”。 这些插槽充满了方法。 “究竟调用了什么方法? 要求你弄清楚两件事情:

  1. 每个插槽的内容是什么?
  2. 哪个槽被称为?

首先考虑插槽。 有两个插槽。 A的所有实例都需要有一个插槽,我们将调用GetNameSlotA。 C的所有实例都需要有一个插槽,我们将调用GetNameSlotC。 这就是C语言中“新”的含义,意思是“我想要一个新的插槽”。 与B中声明的“覆盖”相比,意思是“我不想要一个新的插槽,我想重新使用GetNameSlotA”。

当然,C从Ainheritance,所以C也必须有一个GetNameSlotA槽。 因此,C的实例有两个插槽 – GetNameSlotA和GetNameSlotC。 A或B的实例不是C有一个插槽,GetNameSlotA。

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

A的声明说“把GetNameA放入GetNameSlotA”。 A是C的超类,所以A的规则适用于C

B的声明说“把GetNameB放入GetNameSlotA”。 B是C的超类,所以B的规则适用于C的实例。现在我们有A和B之间的冲突。B是更多的派生types,所以它赢 – B的规则覆盖 A的规则。 因此,声明中的“覆盖”一词。

C的声明说“把GetNameC放在GetNameSlotC”中。

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

现在我们已经确定什么方法在什么位置,所以我们已经回答了我们的第一个问题。

现在我们必须回答第二个问题。 什么槽被称为?

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

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

这里关键的一点是, 在C#重载解决scheme中select一个插槽,并生成对该插槽中发生的任何事情的调用。

它应该返回“B”,因为B.GetName()被保存在A.GetName()函数的小虚拟表格中。 C.GetName()是一个编译时的“覆盖”,它不覆盖虚拟表,所以你不能通过指向A的指针来检索它。

很容易,你只需要记住inheritance树。

在你的代码中,你持有一个types为'A'的类的引用,这个类由'C'types的实例实例化。 现在,为了解决虚拟“GetName()”方法的确切的方法地址,编译器将inheritance层次结构并寻找最近的覆盖 (注意,只有'虚拟'是一个覆盖,'new'是完全不同的…)。

总之,发生了什么事情。 types'C'中的新关键字只有在您将其称为“C”types的实例时才起作用,编译器则会完全否定所有可能的inheritance关系。 严格地说,这与多态性完全没有任何关系 – 你可以看到,无论你使用“new”关键字掩盖虚拟还是非虚拟方法都没有什么区别。

'C'类中的'New'意思就是:如果你对这个(确切)types的实例调用了'GetName()',那么就忘记了所有的东西并使用THIS方法。 '虚拟'意思相反:继续树继续,直到find一个具有这个名字的方法,不pipe调用实例的确切types是什么。

好吧,这个post有点老了,但是这是一个很好的问题,也是一个很好的答案,所以我只是想补充一下我的想法。

考虑下面的例子,和以前一样,除了main函数:

 // 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! 

现在很明显,函数调用在编译时不能绑定到特定的函数。 必须编译一些东西,而这些信息只能依赖于引用的types。 所以,用typesC之外的任何引用来执行类C的GetName函数是不可能的

PS也许我应该使用术语方法来代替函数,但正如莎士比亚所说:任何其他名字的函数仍然是一个函数:)

实际上,我认为它应该显示C,因为new运算符只隐藏所有同名的祖先方法。 所以,隐藏A和B的方法,只有C保持可见。

http://msdn.microsoft.com/en-us/library/51y09td4%28VS.71%29.aspx#vclrfnew_newmodifier