1
class Program
{
    static void Main(string[] args)
    {
        Parent p = new Child();
        p.Print();
    }
}
class Parent
{
    public virtual void Print()
    {
        Console.WriteLine("This is parent.");
    }
}
class Kid:Parent
{
    public override void Print()
    {
        Console.WriteLine("This is Kid.");
    }
}
class Child : Kid
{
    public new virtual void Print()
    {
        Console.WriteLine("This is Child.");
    }
}  

为什么输出是“This is Kid”,而不是“This is child”?

Print()课堂Child是虚拟的。
我试图了解正在发生的事情。

4

5 回答 5

1

您正在调用Print.Parent

new如果您将该变量键入为 ,则运算符将起作用Kid。在这种情况下,new 不是覆盖而是标识符重用

于 2012-08-14T10:10:35.620 回答
1

p是类型Parent。所以编译器在类中寻找一个Print方法。Parent由于这个方法是虚拟的,它会在Kid类中找到一个被覆盖的方法。因为您没有覆盖而是替换中的Print方法Child,所以编译器不会使用此方法。

于 2012-08-14T10:10:43.720 回答
0

以下都过度简化并将一种可能的实现视为事实,但应该足以拥有一个有效的心理模型。

当调用代码“知道”一个类时,它知道以下内容:

  1. 可以在距对象位置的特定偏移处访问字段。例如,如果一个对象位于地址 120,并且有两个整数字段,那么它可能能够在地址 124 访问它们。如果另一个相同类型的对象位于地址 140,则等效字段将位于 144。

  2. 非虚拟方法(和属性可以被认为是一个或两个方法的语法糖)是位于特定地址的函数,它引用您正在调用的对象(this从方法内部)和该函数的其他参数。

  3. 虚拟方法与上面类似,但它们的地址可以通过查看与类关联的表中的特定偏移量来找到,该表的地址也将是类地址的特定偏移量。

在此,Kid有一个方法表,它是Parent(它可以添加更多方法)的超集,并且对于那些它没有覆盖的方法具有相同的函数地址(调用Equals它使用与调用相同的函数Equals在 a 上Parent),但它覆盖的地址不同(Print()在这种情况下)。

因此,如果你有一个Kidthen 无论你是通过Parent引用还是Kid引用,调用Print()都会查看同一个表,查找Print()方法的位置,然后调用它。

在 的情况下Childnew使用Print方法。这告诉编译器我们特别想要一个不同的表。因此,如果我们Print()通过Child引用调用,它会查找Child特定表,并调用它找到的方法。Kid但是,如果我们通过 a or引用调用它Parent,那么我们甚至不知道Child我们可以使用一个特定的表,我们分别在我们知道KidParent拥有的表中查找函数,并调用找到的函数(即中定义Kid)。

作为一项规则,new是要避免的。它的用途有两个地方:

一是向后兼容。例如,如果Child有一个Name属性,然后后来Parent更改了 for 的代码,使其也有一个Name属性,我们就会发生冲突。由于Child'sName不是一个覆盖,它被视为好像它有new但给我们一个警告,因为这是使用旧事物方式的代码和知道新事物的代码可以共存的唯一Name方式Parent。如果我们回来重新编译Child,我们可能应该重构使其没有自己的Name(如果 onParent做我们想要的),重构使其成为覆盖,重构为完全不同的东西,或添加new以表明这就是我们想要的样子,尽管它并不理想。

另一种是new允许基类方法允许的相同行为的更具体形式,但在逻辑上兼容(因此用户不会感到惊讶)。后者应该进入半高级技术框,不要轻易完成。也应该这样评论,因为大多数时候看到new意味着你处理的东西充其量是一种妥协,可能应该改进。

Kid(旁白:我是唯一一个看到s有Child仁就想到小报的人吗?)

于 2012-08-14T10:35:13.427 回答
0

来自 MSDN(http://msdn.microsoft.com/en-us/library/6fawty39 (v=vs.100).aspx ):

“当在 Derived 的实例上调用 DoWork 时,C# 编译器将首先尝试使调用与最初在 Derived 上声明的 DoWork 版本兼容。覆盖方法不被视为在类上声明,它们是方法的新实现在基类上声明。只有当 C# 编译器无法将方法调用匹配到 Derived 上的原始方法时,它才会尝试匹配对具有相同名称和兼容参数的重写方法的调用。

看起来您在 Kid 中使用了“new”,但将 p 声明为 Parent 意味着 p 无法在 Child 中看到 Print,因为它不是继承层次结构的一部分。

static void Main(string[] args)
{
    Parent p = new Child();
    p.Print();

    Child c = (Child) p;
    c.Print();
}

...显然改变了事情。

于 2012-08-14T10:18:38.827 回答
0

好吧,因为您使用了 new 关键字,这意味着您隐藏了继承的方法并提供了新的实现。这通常称为隐藏父成员。

于 2012-08-14T10:11:04.950 回答