关于语法:C# – 关键字用法virtual + override与new

C# - Keyword usage virtual+override vs. new

在基类型"EDOCX1"〔0〕中声明方法,然后在子类型中使用"EDOCX1"〔1〕关键字重写该方法,与在子类型中声明匹配方法时仅使用"EDOCX1"〔2〕关键字有什么区别?


我总是觉得这样的事情用图片更容易理解:

再一次,用约瑟夫·戴格尔的密码,

1
2
3
4
5
6
7
8
9
public class Foo
{
     public /*virtual*/ bool DoSomething() { return false; }
}

public class Bar : Foo
{
     public /*override or new*/ bool DoSomething() { return true; }
}

如果您这样调用代码:

1
2
Foo a = new Bar();
a.DoSomething();

注意:重要的是我们的对象实际上是一个Bar,但是我们将它存储在一个Foo类型的变量中(这与强制转换类似)。

然后,根据您在声明类时是使用virtual/override还是new,结果如下。

Virtual/Override explanation image


"new"关键字不重写,它表示与基类方法无关的新方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Foo
{
     public bool DoSomething() { return false; }
}

public class Bar : Foo
{
     public new bool DoSomething() { return true; }
}

public class Test
{
    public static void Main ()
    {
        Foo test = new Bar ();
        Console.WriteLine (test.DoSomething ());
    }
}

如果使用override,它将打印为true。

(基础代码取自Joseph Daigle)

因此,如果您正在执行真正的多态性,那么应该始终覆盖它。唯一需要使用"new"的地方是当方法与基类版本没有任何关联时。


下面是一些代码来理解虚拟方法和非虚拟方法的行为差异:

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
class A
{
    public void foo()
    {
        Console.WriteLine("A::foo()");
    }
    public virtual void bar()
    {
        Console.WriteLine("A::bar()");
    }
}

class B : A
{
    public new void foo()
    {
        Console.WriteLine("B::foo()");
    }
    public override void bar()
    {
        Console.WriteLine("B::bar()");
    }
}

class Program
{
    static int Main(string[] args)
    {
        B b = new B();
        A a = b;
        a.foo(); // Prints A::foo
        b.foo(); // Prints B::foo
        a.bar(); // Prints B::bar
        b.bar(); // Prints B::bar
        return 0;
    }
}


new关键字实际上创建了一个只存在于该特定类型上的全新成员。

例如

1
2
3
4
5
6
7
8
9
public class Foo
{
     public bool DoSomething() { return false; }
}

public class Bar : Foo
{
     public new bool DoSomething() { return true; }
}

这两种类型上都存在该方法。当使用反射并获取Bar类型的成员时,实际上会发现两个名为DoSomething()的方法看起来完全相同。通过使用new可以有效地隐藏基类中的实现,这样当类从Bar派生时(在我的示例中),对base.DoSomething()的方法调用将转到Bar而不是Foo


virtual/override告诉编译器这两个方法是相关的,并且在某些情况下,当您认为正在调用第一个(virtual)方法时,实际上调用第二个(override)方法是正确的。这是多态性的基础。

1
(new SubClass() as BaseClass).VirtualFoo()

将调用子类的overriden virtualfoo()方法。

new告诉编译器您正在向派生类添加一个方法,该方法与基类中的方法同名,但它们之间没有关系。

1
(new SubClass() as BaseClass).NewBar()

将调用基类的newbar()方法,但是:

1
(new SubClass()).NewBar()

将调用子类的newbar()方法。


除了技术细节之外,我认为使用虚拟/覆盖在设计上传递了很多语义信息。当您声明一个方法是虚拟的时,您表示您希望实现类可以提供它们自己的非默认实现。同样,在基类中省略这一点也声明了这样一种期望,即默认方法应该足以满足所有实现类。类似地,可以使用抽象声明强制实现类提供自己的实现。同样,我认为这传达了很多关于程序员期望如何使用代码的信息。如果我同时编写基类和实现类,发现自己在使用new,我会认真地重新考虑不在父类中使方法虚拟化的决定,并明确声明我的意图。


override关键字和new关键字的区别在于前者进行方法重写,后者进行方法隐藏。

有关详细信息,请查看以下链接…

msdn和其他


  • new关键字用于隐藏。-意味着您正在运行时隐藏方法。输出将基于基类方法。
  • override表示优先。-意味着您正在使用基类的引用调用派生类方法。输出将基于派生类方法。

我的解释版本来自于使用属性帮助理解差异。

override足够简单,对吗?基础类型重写父类型。

new可能是误导(对我来说是这样)。有了属性,更容易理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Foo
{
    public bool GetSomething => false;
}

public class Bar : Foo
{
    public new bool GetSomething => true;
}

public static void Main(string[] args)
{
    Foo foo = new Bar();
    Console.WriteLine(foo.GetSomething);

    Bar bar = new Bar();
    Console.WriteLine(bar.GetSomething);
}

使用调试器,您可以注意到Foo foo有2个GetSomething属性,因为它实际上有2个版本的属性,FooBar的属性,为了知道要使用哪一个,c"选择"当前类型的属性。

如果您想使用BAR的版本,您应该使用override或使用Foo foo

Bar bar只有1个,因为它希望GetSomething有全新的行为。


不以任何方式标记方法:使用对象的编译类型而不是运行时类型(静态绑定)绑定此方法。

使用virtual标记方法意味着:使用对象的运行时类型绑定此方法,而不是编译时类型(动态绑定)。

在派生类中用override标记基类virtual方法意味着:这是使用对象的运行时类型(动态绑定)绑定的方法。

在派生类中用new标记一个基类virtual方法意味着:这是一个新方法,它与基类中同名的方法没有关系,应该使用对象的编译时类型(静态绑定)进行绑定。

在派生类中不标记基类virtual方法意味着:此方法被标记为new(静态绑定)。

标记一个方法abstract意味着:这个方法是虚拟的,但我不会为它声明主体,它的类也是抽象的(动态绑定)。