【问题标题】:Overriding vs method hiding [duplicate]覆盖vs方法隐藏[重复]
【发布时间】:2011-04-19 19:23:48
【问题描述】:

我对覆盖和隐藏 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# 编程概念,然后才能理解方法覆盖和隐藏。尝试周围众多的 C# 初学者书籍之一。
  • 1) a.m1(); // 这将打印什么,为什么?简单地说,它会打印"hai to all",因为您使用的是方法覆盖概念。 2) b = a; // 这里发生了什么?这里我们会得到一个编译错误,因为子类对象不会引用基类对象。根据继承概念,反向是可能的。
  • 这不是标记为副本的副本

标签: c# overriding method-hiding


【解决方案1】:

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

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

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

例如,考虑这些类:

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 Method2

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

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

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

运行 InheritedClass 方法1
运行 BaseClass Method2

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


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

【讨论】:

  • 我不会说你应该永远隐藏一个方法。有时,我发现隐藏非常有用(例如,将返回值更改为子类)
  • 这就是为什么我说你应该-尝试-永远不要这样做:)
  • 是的,但如果@thecoop 提到您想非常刻意地尝试隐藏。我认为主要的事情是,通常隐藏会带来令人讨厌的惊喜,但在将它用于协方差时,你实际上会消除惊喜。
  • 隐藏方法的“特性”是否违反了 LSP?
  • @brighty 对非常迟到的回复表示歉意,您可能已经找到该信息,但是是的,它确实违反了 LSP。事实上,这基本上就是“隐藏”所违反的 LSP 的核心。
【解决方案2】:

考虑:

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 中定义的功能。

隐藏意味着我们有一个完全不同的方法。当我们在isReallyDerived 上调用WriteNum() 时,我们无法知道DerivedClass 上存在不同的WriteNum(),因此它不会被调用。它只能在我们处理对象asDerivedClass时调用。

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

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

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

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

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

【讨论】:

  • 先生,如果我使用 public virtual void WriteNum() 并在子类中......我使用 public new virual void WriteNum() 将在 isReallyBase.WriteNum() 上调用什么方法;和你
  • @NoviceToDotNet using new virtual 意味着它隐藏了更多派生版本,但它本身可以是虚拟的。如果您在isReallyBaseisReallyDerived 上调用WriteNum(),则将调用BaseClass 中定义的版本,因为它不会被覆盖,但如果您在isClearlyDerived 上调用它,则将调用隐藏版本.此外,您可以在另一个子类中进行覆盖(例如“DerivedFromDerivedClass”),并且当通过 DerivedFromDerivedClass 变量或 DerivedClass 变量操作 DerivedFromDerivedClass 时,将调用此覆盖...
  • 如果我们不重写超类 bcas 的方法,这不是强制性的,那么它会做的是ReallyDerived.WriteStr();
  • ... 但是如果 DerivedFromDerivedClass 通过 BaseClass 变量调用它,那么只会调用在 BaseClass 中定义的变量。一种思考方式是基类“知道”虚拟成员可以被覆盖(并且覆盖成员可以再次被覆盖),并且会“寻找”这些覆盖,但它们不能“看到过去”任何隐藏的成员.
  • 先生 1 困惑你告诉“当一个虚拟方法在一个对象上被调用时,那么该方法的最派生版本被调用。”...... .then y isReallyBase.WriteStr(); // 写 abc .. ......不是 prinithing 写 xyz
【解决方案3】:

方法覆盖只是在派生类中覆盖基类方法的默认实现。

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

作为

class Foo  
{  
  public virtual void foo1()  
  {  

  }  
}  

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

  }  
}  

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

【讨论】:

  • 如果在您的示例中我这样做,您可以使用带有覆盖关键字的 virtual ....class myclass:bar{ 那么它将获得 Foo 类 foo1() 或 bar 类 foo1() 来覆盖如果在 bar 类中隐藏 foo1() 我覆盖它并且我在它之前放置 virtual 那么它将被继承到 myClass 并且再次通过放置覆盖我可以覆盖 Bar 类 foo1() 到 myClass 的方法跨度>
  • @NoviceToDotNet 不,virtual 可以在派生自 Bar 的类中使用,但它将覆盖 Bar.foo1 而不是 Foo.foo1。上述隐藏和虚拟的混合是有效的,尽管将两者结合起来可能不是对两者之间差异的最佳解释。
猜你喜欢
  • 2013-01-16
  • 2014-08-13
  • 1970-01-01
  • 1970-01-01
  • 2015-10-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多