关于.net:为什么这个多态C#代码会打印它的作用?

Why does this polymorphic C# code print what it does?

我最近得到了下面的代码,作为帮助理解OOP-C中的PolymorphismInheritance的一种困惑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 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,因为这似乎是定义的类。接着,我仔细考虑了一下,是不是因为C继承了B,所以B会不会被归还——但B也继承了A(我搞糊涂了!).

问题:

有人能解释多态性和继承如何在检索最终显示在屏幕上的输出中发挥作用吗?


正确的思考方法是设想每个类都要求其对象具有一定数量的"槽";这些槽中充满了方法。问题"实际调用了什么方法?"需要你想出两件事:

  • 每个插槽的内容是什么?
  • 叫哪个槽?
  • 让我们从考虑插槽开始。有两个插槽。a的所有实例都需要有一个槽,我们称之为getnameslota。C的所有实例都需要有一个称为getnamesloc的槽。这就是C语言声明中的"new"的意思——它的意思是"我想要一个新的槽"。与B中声明中的"override"相比,这意味着"我不想要新的槽,我想要重用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的规则。因此,在声明中使用"override"一词。

    C的声明是"将getnamec放入getnamesloc"。

    因此,您的新C将有两个插槽。getnameslota将包含getnameb,getnameslotc将包含getnamec。

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

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

    把它当作编译器来考虑。你有一个变量。您只知道它是A类型的。您需要解析对该变量的方法调用。您可以查看A上可用的插槽,唯一可以找到匹配的插槽是getnameslota。您不知道getnameslotc,因为您只有一个类型为A的变量;为什么要查找只适用于C的槽?

    因此,这是对getnameslota中的任何内容的调用。我们已经确定在运行时,getnameb将在那个位置。因此,这是对getnameb的调用。

    这里的关键是,在C过载分辨率中,选择一个插槽并生成对该插槽中发生的任何事件的调用。


    它应该返回"b",因为B.GetName()被保存在A.GetName()函数的小虚拟表框中。C.GetName()是编译时的"override",它不会重写虚拟表,因此您不能通过指向A的指针来检索它。


    简单地说,你只需要记住继承树。

    在代码中,您持有对"A"类型的类的引用,该类由"C"类型的实例实例化。现在,为了解析虚拟"getname()"方法的确切方法地址,编译器将进入继承层次结构并查找最新的重写(请注意,只有"virtual"是一个重写,"new"是完全不同的东西…)。

    简而言之,就是这样。类型"c"中的新关键字仅在您在类型"c"的实例上调用时才起作用,然后编译器将完全否定所有可能的继承关系。严格来说,这与多态性毫无关系——你可以从你用"new"关键字屏蔽一个虚拟或非虚拟方法的事实中看到,没有任何区别…

    类"c"中的"new"正好意味着:如果您在此(精确)类型的实例上调用"getname()",则忽略所有内容并使用此方法。相反,"virtual"的意思是:在继承树上查找具有此名称的方法,不管调用实例的确切类型是什么。


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

    考虑下面的示例,除主功能外,与前面的示例相同:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    // 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.也许我应该用"方法"这个词来代替"函数",但正如莎士比亚所说:任何其他名称的函数仍然是一个函数:)


    实际上,我认为它应该显示C,因为新的操作符只隐藏具有相同名称的所有祖先方法。因此,在隐藏a和b方法的情况下,只有c仍然可见。

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