62

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

3 回答 3

143

考虑:

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 方式,派生类可以比基类具有更具体的行为(在某些语言中,您别无选择,只能这样做)。当对对象调用虚拟方法时,将调用该方法的最派生版本。因此,即使我们正在处理中定义isReallyDerivedBaseClassthen 功能DerivedClass

隐藏意味着我们有一个完全不同的方法。当我们调用时WriteNum(),我们isReallyDerived无法知道有一个不同WriteNum()的 on,DerivedClass所以它不会被调用。只有当我们将对象作为DerivedClass.

大多数时候隐藏是不好的。通常,如果它可能在派生类中更改,则您应该将方法设置为虚拟方法,并在派生类中覆盖它。然而,它有两件事是有用的:

  1. 前向兼容性。如果DerivedClass有一个DoStuff()方法,然后后来BaseClass更改为添加一个DoStuff()方法,(请记住,它们可能由不同的人编写并存在于不同的程序集中),那么禁止成员隐藏会突然变得有问题DerivedClass而不改变它。此外,如果新DoStuff()的 onBaseClass是虚拟的,那么自动DerivedClass覆盖它可能会导致预先存在的方法在不应该被调用时被调用。因此,隐藏是默认设置是很好的(我们使用new它来明确我们肯定想要隐藏,但将其排除在外会隐藏并在编译时发出警告)。

  2. 穷人协方差。考虑一个Clone()方法,BaseClass它返回一个新BaseClass的,它是创建的副本。在DerivedClass对此的覆盖中将创建 aDerivedClass但将其返回为 a BaseClass,这不是很有用。我们可以做的是有一个CreateClone()被覆盖的虚拟保护。在BaseClass我们有 aClone()返回 this 的结果 - 一切都很好 -DerivedClass我们用一个Clone()返回 a的 new 隐藏它DerivedClass。调用Clone()onBaseClass将始终返回一个BaseClass引用,该引用将是一个BaseClass值或DerivedClass适当的值。调用Clone()DerivedClass返回一个DerivedClass值,这是我们在这种情况下想要的。该原理还有其他变体,但应该注意的是,它们都非常罕见。

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

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

于 2010-10-01T11:22:07.403 回答
29

覆盖是当您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 方法 1
运行 InheritedClass 方法2

这段代码创建了同一个 InheritedClass 的实例,但将其存储在 BaseClass 引用中:

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

通常,在 OOP 原则下,您应该期望与上述示例相同的输出。但是你没有得到相同的输出:

运行 InheritedClass 方法 1
运行 BaseClass 方法2

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


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

于 2010-10-01T11:12:02.967 回答
2

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

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

作为

class Foo  
{  
  public virtual void foo1()  
  {  

  }  
}  

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

  }  
}  

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

于 2010-10-01T11:13:33.527 回答