18

继承继承的类时,新的 / 覆盖行为不是我所期望的:

$ cat Program.cs
using System;

class A {
    public virtual void SayHi() {
        Console.WriteLine("From A");
    }
}
class B : A { 
    public new virtual void SayHi()  {
        Console.WriteLine("From B");
    }
}
class C : B { 
    public override void SayHi() {
        Console.WriteLine("From C");
    }
}

public class Program {
    public static void Main() {
        A p = new C();
        p.SayHi();
    }
}

$ ./Program.exe 
From A

由于 C 类覆盖了 sayHi() 方法,我希望输出为From C. 为什么B类的new修饰符在这里优先?那有什么用例?特别是因为它打破了让 C 真正覆盖 A 的明显用例。

请注意,上面的代码是在 Mono 2.10 上运行的,该版本在 Debian 派生发行版上运行。但我已经使用 MS Visual Studio 中的 C# 编译器确认了相同的行为。

4

5 回答 5

21

new修饰符导致成员隐藏,从而破坏了类层次结构中的多态关系。的SayHi方法B被视为与 's不同(而不是覆盖)A(因此选择单词“new”作为关键字)。C's 方法然后覆盖B's,而不是A's(仍然隐藏)。

因此,当您通过引用调用实例SayHi时,运行时会将其解析为类型,而不是类型(其中是从 继承的“新”方法)。CAACSayHiB

另一方面,如果您要运行:

B p = new C();
p.SayHi();

…你会得到预期的多态结果:

From C

编辑:由于您请求了一个用例,这是一个。在 .NET Framework 2.0 中引入泛型之前,成员隐藏有时被用作更改派生类中继承方法的返回类型的一种方法(在重写时不能这样做),以返回更具体的类型。例如:

class ObjectContainer
{
    private object item;

    public object Item 
    {
        get { return item; }
        set { item = value; }
    }
}

class StringContainer : ObjectContainer
{
    public new virtual string Item
    {
        get { return base.Item as string; }
        set { base.Item = value as string; }
    }
}

class QuotedStringContainer : StringContainer
{
    public override string Item
    {
        get { return "\"" + base.Item + "\""; }
    }
}

类的Item属性ObjectContainer返回一个普通的object. 但是,在 中,这个继承的属性被隐藏了,而是StringContainer返回 a 。string因此:

ObjectContainer oc = new StringContainer();
object o  = oc.Item;   // Valid, since ObjectContainer.Item is resolved
string s1 = oc.Item;   // Not valid, since ObjectContainer.Item is still resolved
string s2 = ((StringContainer)oc).Item;   
                       // Valid, since StringContainer.Item is now resolved

该类QuotedStringContainer覆盖 的Item属性StringContainer,继承其string返回类型;但是,它仍然隐藏在 的object-returningItem属性中ObjectContainer。如果不是这样,就没有办法调和它们不同的返回类型……</p>

ObjectContainer oc = new QuotedStringContainer();
object o  = oc.Item;   // Valid, since ObjectContainer.Item is resolved
string s1 = oc.Item;   // Not valid, since ObjectContainer.Item is still resolved
string s2 = ((StringContainer)oc).Item;   
                       // Valid, since QuotedStringContainer.Item is now resolved
                       // (polymorphism!)
string s3 = ((QuotedStringContainer)oc).Item;   
                       // Valid, since QuotedStringContainer.Item is now resolved
于 2012-06-14T18:48:28.757 回答
7

C正在覆盖方法的阴影版本(在中被阴影B),而不是在A.

结果,当您使用类型的变量时ASayHi定义的 inA被调用,因为没有被覆盖 in C

于 2012-06-14T18:48:33.440 回答
6

C覆盖B的方法,因此当您将其转换为 时A,您最终会调用A.

请参阅ECMA 334: 17.5.3 下的 C# 语言规范是您的示例(第 294 页)。

于 2012-06-14T18:50:25.293 回答
2

因为 C 类没有覆盖 A 类中的 SayHi 方法,它覆盖了 B 中的“新”方法。由于您的强制转换是 A,编译器将其解析为对 A.SayHi() 的调用而不是 C。打招呼()

于 2012-06-14T18:50:59.277 回答
2

此 msdn 页面的最后一个示例详细解释了此处发生的情况。

基本上, new 修饰符会导致 A 中的方法对 C 隐藏,并且由于在 C 覆盖时它是公共的,因此它会覆盖 B 中的方法(将其视为自己的不同方法)。

如果将 B 中的方法设置为私有,C 将再次覆盖 A 中的方法。

class B : A
{
    private new void SayHi()
    {
        Console.WriteLine("From B");
    }
}

结果是:From C

于 2012-06-14T18:52:47.007 回答