新建和覆盖的区别

277

想知道以下两者之间的区别:

情况1:基类

public void DoIt();

情况1:继承类

public new void DoIt();

情况2:基类

public virtual void DoIt();

情况2:继承类

public override void DoIt();

根据我运行的测试,情况1和情况2似乎具有相同的效果。是否存在区别或首选方法?


3
重复的问题,包括https://dev59.com/NXVC5IYBdhLWcg3w21Mq。 翻译:这是一个重复的问题,包括在https://dev59.com/NXVC5IYBdhLWcg3w21Mq中提到的问题。 - Jon Skeet
14个回答

355

override修饰符可以用在虚方法上,并且必须用在抽象方法上。这表明编译器应使用方法的最后定义实现。即使该方法是通过对基类的引用调用,它也将使用覆盖它的实现。

public class Base
{
    public virtual void DoIt()
    {
    }
}

public class Derived : Base
{
    public override void DoIt()
    {
    }
}

Base b = new Derived();
b.DoIt();                      // Calls Derived.DoIt

如果Derived覆盖了Base.DoIt,那么会调用Derived.DoIt

new修饰符指示编译器使用子类实现而不是父类实现。任何没有引用子类而是引用父类的代码将使用父类实现。

public class Base
{
    public virtual void DoIt()
    {
    }
}

public class Derived : Base
{
    public new void DoIt()
    {
    }
}

Base b = new Derived();
Derived d = new Derived();

b.DoIt();                      // Calls Base.DoIt
d.DoIt();                      // Calls Derived.DoIt

将首先调用 Base.DoIt,然后调用 Derived.DoIt。它们实际上是两个完全独立的方法,只是恰好具有相同的名称,并不是派生方法覆盖了基本方法。

来源:Microsoft 博客


8
这表示编译器将使用方法的最后定义实现。如何找到方法的最后定义实现? - AminM
8
从一个具体的类开始,检查它是否有所需方法的实现。如果有,就完成了。如果没有,则向上遍历继承层次结构,也就是检查超类是否有所需的方法。继续这个过程,直到找到所需的方法。 - csoltenborn
5
请注意,只有基类将方法定义为“virtual”时,才能“override”(覆盖)该方法。 “virtual” 表示基类在调用此方法时,该方法实现实际上可能已被派生类替换,因此在运行时无法预先知道要调用的方法实现。因此,“virtual” 表示方法的占位符。这意味着未标记为“virtual”的方法不能被覆盖。但是,在派生类中使用“new”修饰符可以替换任何非虚方法,且仅可在派生级别访问。 - Erik Bongers
2
如果您在派生类中直接调用 base.DoIt(),会发生什么? - Joe Phillips

206

virtual: 表示一个方法可以被继承者覆盖重写。

override: 覆盖基类中一个虚拟方法的功能,提供不同的功能。

new: 隐藏原始方法(不必是虚拟的),提供不同的功能。这应该只在绝对必要的情况下使用。

当您隐藏一个方法时,仍然可以通过向上转换到基类访问原始方法。这在某些情况下很有用,但也很危险。


3
为什么向上转型会隐藏基类方法,这种方法为什么危险?或者你是在暗示向上转型总体上都是危险的吗? - John Evans Solachuk
8
@Mark - 一个调用者可能并不了解具体实现,会导致意外的误用。 - Jon B
你能在父方法上不使用 virtual 关键字的情况下使用 override 和/或 new 吗? - Aaron Franke
@Mark:因为如果你将它转换为基本类型,它会使用隐藏的方法而不是“new”方法。如果你明确地实现接口,这种情况肯定会发生。 - Stefan Steiger

21
在第一种情况下,你将定义隐藏在父类中。这意味着只有当你将对象作为子类处理时,它才会被调用。如果你将该类转换为其父类型,则会调用父类的方法。在第二种情况下,方法被覆盖并且将被调用,无论对象是作为子类还是父类进行强制转换。

12

enter image description here

所有的组合包括none、virtual、override、new和abstract:


5
这张表格值得更多的关注。它比官方文档中涵盖该主题的许多页面更有帮助,可以作为一个提醒。 - ScienceSnake

11
  • new表示尊重你的引用类型(赋值语句左侧),从而运行引用类型的方法。如果重新定义的方法没有new关键字,则会被视为有该关键字。此外,它也被称为非多态继承。也就是说,“我正在派生类中创建一个全新的方法,与基类中任何同名方法都没有任何关系。” - 威特克(Whitaker)所说。
  • override必须与其基类中的virtual关键字一起使用,表示尊重你的对象类型(赋值语句右侧),从而无论引用类型如何,都会运行被覆盖的方法。此外,它也被称为多态继承

我记住这两个关键字的方式是将它们视为相反的。

override: 必须定义virtual关键字来覆盖方法。使用override关键字的方法会忽略引用类型(基类或派生类的引用),如果用基类实例化,那么就会运行基类的方法;否则,将运行派生类的方法。

new:如果方法使用了该关键字,与override关键字不同,引用类型就很重要了。如果用派生类实例化并且引用类型是基类,那么将运行基类的方法;如果用派生类实例化并且引用类型是派生类,那么将运行派生类的方法。也就是说,它是override关键字的反义词。另外,如果你忘记或省略添加new关键字到方法中,编译器会默认将其视为已使用new关键字。

class A 
{
    public string Foo() 
    {
        return "A";
    }

    public virtual string Test()
    {
        return "base test";
    }
}

class B: A
{
    public new string Foo() 
    {
        return "B";
    }
}

class C: B 
{
    public string Foo() 
    {
        return "C";
    }

    public override string Test() {
        return "derived test";
    }
}

在主函数中调用:

A AClass = new B();
Console.WriteLine(AClass.Foo());
B BClass = new B();
Console.WriteLine(BClass.Foo());
B BClassWithC = new C();
Console.WriteLine(BClassWithC.Foo());

Console.WriteLine(AClass.Test());
Console.WriteLine(BClassWithC.Test());

输出:

A
B
B
base test
derived test

新的代码示例:

通过逐一注释来尝试代码。

class X
{
    protected internal /*virtual*/ void Method()
    {
        WriteLine("X");
    }
}
class Y : X
{
    protected internal /*override*/ void Method()
    {
        base.Method();
        WriteLine("Y");
    }
}
class Z : Y
{
    protected internal /*override*/ void Method()
    {
        base.Method();
        WriteLine("Z");
    }
}

class Programxyz
{
    private static void Main(string[] args)
    {
        X v = new Z();
        //Y v = new Z();
        //Z v = new Z();
        v.Method();
}

"Override是针对赋值语句右侧的,new是针对赋值语句左侧的。这是一个好的记忆方式,谢谢!" - Ferazhu

8
尝试以下操作:(情况1)
((BaseClass)(new InheritedClass())).DoIt()

编辑:virtual+override在运行时解决(所以override确实覆盖虚拟方法),而new只是创建一个同名的新方法,并隐藏旧方法,它在编译时解决 ->您的编译器将调用它“看到”的方法


4

如果您在将类型声明为基类时调用继承类的DoIt()方法,则即使如此,您也将看到基类的操作。

在情况1中。
/* Results
Class1
Base1
Class2
Class2
*/
public abstract class Base1
{
    public void DoIt() { Console.WriteLine("Base1"); }
}
public  class Class1 : Base1 
{
    public new void DoIt() { Console.WriteLine("Class1"); }
}
public abstract class Base2
{
    public virtual void DoIt() { Console.WriteLine("Base2"); }
}
public class Class2 : Base2
{
    public override void DoIt() { Console.WriteLine("Class2"); }
}
static void Main(string[] args)
{
    var c1 = new Class1();
    c1.DoIt();
    ((Base1)c1).DoIt();

    var c2 = new Class2();
    c2.DoIt();
    ((Base2)c2).DoIt();
    Console.Read();
}

你能否发布你所收到的警告或错误信息。当我最初发布这段代码时,它是可以正常工作的。 - Matthew Whited
这些内容应该全部粘贴到你的入口类(Program)中。为了在这个网站上更好地格式化,它被移除了。 - Matthew Whited

4

这两种情况的区别在于,在第一种情况中,基本的DoIt方法没有被覆盖,只是被隐藏了。这意味着根据变量类型的不同,调用哪个方法也会不同。例如:

BaseClass instance1 = new SubClass();
instance1.DoIt(); // Calls base class DoIt method

SubClass instance2 = new SubClass();
instance2.DoIt(); // Calls sub class DoIt method

这可能会让人感到困惑,并导致非预期的行为,如果可能的话,应该避免使用这种方式。所以,首选的方法是第二种情况。


3

我曾经有同样的问题,这真的很令人困惑。你应该考虑到overridenew关键字只适用于基类对象和派生类值的情况。只有在这种情况下,你才会看到overridenew的效果:

所以如果你有AB两个类,B继承自A,那么你可以像这样实例化一个对象:

A a = new B();

现在调用方法将考虑其状态。 覆盖:意味着它扩展了方法的功能,然后在派生类中使用该方法,而 new 告诉编译器隐藏派生类中的方法并使用基类中的方法。以下是一个非常好的网址,介绍了这个主题:https://msdn.microsoft.com/EN-US/library/ms173153%28v=VS.140,d=hv.2%29.aspx?f=255&MSPPError=-2147217396

3
下面的文章是使用vb.net编写的,但我认为关于new和overrides的解释非常容易理解。
请参考以下链接:https://www.codeproject.com/articles/17477/the-dark-shadow-of-overrides 在文章的某个地方,有这样一句话:
总的来说,Shadows假设与类型相关联的函数被调用,而Overrides假设执行对象实现。
这个问题的接受答案很完美,但我认为这篇文章提供了很好的例子,以更好地说明这两个关键字之间的区别。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接