覆盖vs方法隐藏

时间:2010-10-01 11:03:36

标签: c# override method-hiding

我对覆盖与隐藏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?
}

3 个答案:

答案 0 :(得分:121)

考虑:

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

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

隐藏意味着我们有一种完全不同的方法。当我们在WriteNum()上致电isReallyDerived时,我们无法知道WriteNum()上有DerivedClass,因此不会被调用。它只能在我们处理对象 a 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值,这是我们在该上下文中所需的值。这个原则还有其他变体,但应该注意它们都非常罕见。

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

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

答案 1 :(得分:25)

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

隐藏是当您在基类中将 not 定义为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 Method2

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


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

答案 2 :(得分:2)

Method Overriding简单地覆盖派生类中基类方法的默认实现。

方法隐藏:您可以在派生类

中的虚方法之前使用'new'关键字

as

class Foo  
{  
  public virtual void foo1()  
  {  

  }  
}  

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

  }  
}  

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