覆盖vs方法隐藏[重复]

问题描述 投票:52回答:3

这个问题在这里已有答案:

关于覆盖与隐藏C#中的方法有点困惑。每个人的实际用途也将被理解,以及何时使用每个人的解释。

我对重写感到困惑 - 为什么要覆盖?到目前为止我所学到的是,通过覆盖,我们可以在不改变签名的情况下为派生类的方法提供所需的实现。

如果我不覆盖超类的方法并且我对子类中的方法进行了更改,那么是否会更改超类方法?

我也对以下内容感到困惑 - 这表明了什么?

class A
{
    virtual m1()
    {
        console.writeline("Bye to all");
    }
}

class B : A
{
    override m1()
    {
        console.writeLine("Hi to all");
    }
}

class C
{
    A a = new A();
    B b = new B();
    a = b; (what is this)
    a.m1(); // what this will print and why?

    b = a; // what happens here?
}
c# override method-hiding
3个回答
124
投票

考虑:

public class BaseClass
{
  public void WriteNum()
  {
    Console.WriteLine(12);
  }
  public virtual void WriteStr()
  {
    Console.WriteLine("abc");
  }
}

public class DerivedClass : BaseClass
{
  public new void WriteNum()
  {
    Console.WriteLine(42);
  }
  public override void WriteStr()
  {
    Console.WriteLine("xyz");
  }
}
/* ... */
BaseClass isReallyBase = new BaseClass();
BaseClass isReallyDerived = new DerivedClass();
DerivedClass isClearlyDerived = new DerivedClass();

isReallyBase.WriteNum(); // writes 12
isReallyBase.WriteStr(); // writes abc
isReallyDerived.WriteNum(); // writes 12
isReallyDerived.WriteStr(); // writes xyz
isClearlyDerived.WriteNum(); // writes 42
isClearlyDerived.writeStr(); // writes xyz

覆盖是经典的OO方式,其中派生类可以具有比基类更具体的行为(在某些语言中,您别无选择,只能这样做)。在对象上调用虚方法时,将调用该方法的派生程度最高的版本。因此,即使我们将isReallyDerived作为BaseClass处理,然后使用DerivedClass中定义的功能。

隐藏意味着我们有一个完全不同的方法。当我们在WriteNum()上调用isReallyDerived然后就无法知道WriteNum()上有不同的DerivedClass所以它没有被调用。它只能在我们将对象作为DerivedClass处理时调用。

大部分时间隐藏都很糟糕。通常,如果方法可能在派生类中更改,则应将其作为虚方法,并在派生类中覆盖它。然而,它有两件事是有用的:

  1. 向前兼容性。如果DerivedClass有一个DoStuff()方法,然后后来BaseClass改为添加DoStuff()方法,(记住它们可能由不同的人写,并存在于不同的集会)然后禁止成员隐藏将突然使DerivedClass越野车没有它改变。此外,如果DoStuff()上的新BaseClass是虚拟的,那么在DerivedClass上自动对其进行覆盖可能导致在不应该的情况下调用预先存在的方法。因此,隐藏是默认的好处(我们使用new来表明我们肯定想要隐藏,但是将它隐藏起来并在编译时发出警告)。
  2. 穷人的协方差。考虑在Clone()上的BaseClass方法,它返回一个新的BaseClass,它是创建的副本。在DerivedClass的覆盖中,这将创建一个DerivedClass,但将其作为BaseClass返回,这不是很有用。我们可以做的是拥有一个被覆盖的虚拟保护CreateClone()。在BaseClass,我们有一个Clone()返回结果 - 并且一切都很好 - 在DerivedClass我们隐藏这个与新的Clone()返回DerivedClass。在Clone()上调用BaseClass将始终返回BaseClass引用,这将是BaseClass值或DerivedClass值。在Clone()上调用DerivedClass将返回DerivedClass值,这是我们在这种情况下所需要的。这个原则还有其他变体,但是应该注意它们都非常罕见。

第二种情况需要注意的重要一点是,我们使用隐藏精确来消除调用代码的意外,因为使用DerivedClass的人可能会合理地期望它的Clone()返回DerivedClass。可以调用的任何方式的结果都保持一致。大多数隐藏风险的案例引入了意外,这就是为什么他们通常不赞成的原因。这个是正确的,因为它解决了隐藏经常引入的问题。

总之,隐藏有时是必要的,很少有用,但通常很糟糕,所以要非常小心。


25
投票

覆盖是当在基类中将该方法定义为override时,在后代类中提供方法的新virtual实现。

隐藏是当您在基类中未将该方法定义为virtual时,或者当您的新实现未指定override时,在后代类中提供方法的新实现。

隐藏往往很糟糕;你应该尽量不要这样做,如果你可以完全避免它。隐藏可能会导致意外事件发生,因为隐藏方法仅在调用您定义的实际类型的变量时使用,而不是在使用基类引用时...另一方面,被覆盖的虚拟方法最终会被即使在子类上使用基类引用调用时,也会调用正确的方法版本。

例如,考虑以下类:

public class BaseClass
{
  public virtual void Method1()  //Virtual method
  {
    Console.WriteLine("Running BaseClass Method1");
  }
  public void Method2()  //Not a virtual method
  {
    Console.WriteLine("Running BaseClass Method2");
  }
}
public class InheritedClass : BaseClass
{
  public override void Method1()  //Overriding the base virtual method.
  {
    Console.WriteLine("Running InheritedClass Method1");
  }
  public new void Method2()  //Can't override the base method; must 'new' it.
  {
    Console.WriteLine("Running InheritedClass Method2");
  }
}

让我们在匹配的引用中使用InheritedClass的实例来调用它:

InheritedClass inherited = new InheritedClass();
inherited.Method1();
inherited.Method2();

这会返回你应该期待的东西;两种方法都表示它们正在运行InheritedClass版本。

运行InheritedClass Method1 运行InheritedClass Method2

此代码创建相同的实例InheritedClass,但将其存储在BaseClass引用中:

BaseClass baseRef = new InheritedClass();
baseRef.Method1();
baseRef.Method2();

通常,根据OOP原则,您应该期望与上面的示例相同的输出。但是你得不到相同的输出:

运行InheritedClass Method1 运行BaseClass方法2

当您编写InheritedClass代码时,您可能希望对Method2()的所有调用都运行您在其中编写的代码。通常,这将是它的工作原理 - 假设你正在使用你已经覆盖的virtual方法。但是因为您使用的是new / hidden方法,所以它会调用您正在使用的引用上的版本。


如果这是你真正想要的行为,那么;你去吧但我强烈建议如果这就是你想要的,那么代码可能存在更大的架构问题。


2
投票

方法重写是简单地覆盖派生类中基类方法的默认实现。

方法隐藏:您可以在派生类中的虚方法之前使用'new'关键字

class Foo  
{  
  public virtual void foo1()  
  {  

  }  
}  

class Bar:Foo  
{  
  public new virtual void foo1()  
  {   

  }  
}  

现在,如果您创建另一个派生自Bar的类Bar1,您可以覆盖在Bar中定义的foo1。

© www.soinside.com 2019 - 2024. All rights reserved.