【问题标题】:Base method hiding in C# and Java隐藏在 C# 和 Java 中的基本方法
【发布时间】:2015-03-13 11:47:50
【问题描述】:

我来自 Java 背景,目前正在学习 C#。我刚刚对(我认为的)对象从基类/派生类访问方法的方式的差异感到非常惊讶。这就是我的意思:

如果我做这样的事情,在 Java 中

class InheritanceTesting
{
    public void InheritanceOne()
    {
        System.out.println("InheritanceOne");
    }
}

class NewInherit extends InheritanceTesting 
{
    public void InheritanceOne()
    {
        System.out.println("InheritanceTwo");
    } 
 }

然后运行以下命令:

 public static void main(String[] args)    {
    InheritanceTesting inh = new NewInherit();
        inh.InheritanceOne();    
 }

我得到了结果:

InheritanceTwo

如果我在 C# 中做同样的事情:

class InheritanceTesting
{
    public void InheritanceOne()
    {
        Console.WriteLine("InheritanceOne");
    }
}

class NewInherit : InheritanceTesting
{
    public new void InheritanceOne()
    {
        Console.WriteLine("InheritanceTwo");
    }
}

然后:

InheritanceTesting inh = new NewInherit();
        inh.InheritanceOne();

结果是

InheritanceOne

我记得在 Java 中被教导“对象知道它被实例化的类型”,因此,当我调用被覆盖的方法时,这并不奇怪。这是否意味着在 C# 中情况正好相反?对象只“知道”其声明的类型?如果是这样,其中的逻辑/优势是什么?在我看来,Java 将基类视为接口 - 这是您的类型,这是您的实际实现。我是 C# 新手,也许我在这里遗漏了一些明显的东西?

【问题讨论】:

  • 由于 Java 不是 C#,我不明白您对它们可能不同的惊讶。其次,我注意到您的 C# 示例在 InheritanceOne 签名中似乎有一个 new 关键字。
  • 正如我所提到的 - 我只是在学习:)。到目前为止,我遇到的其他功能非常相似,足以让我对这种差异感到惊讶。 (是的,它有new 关键字,所以它隐藏了基类中的方法。就我在这种情况下理解new 而言,它不会影响我的其余问题。再次,正如我所说 -我的问题基本上是“如果我理解这一点,有什么好处”)

标签: java c# inheritance methods


【解决方案1】:

下面是一个稍微有趣的案例

class InheritanceTesting
{
    public void InheritanceOne() 
    // Java equivalent would be
    // public final void InheritanceA()
    {
        Console.WriteLine("InheritanceA - One");
    }


    public virtual void InheritanceB()
    // Java equivalent would be
    // public void InheritanceB() // note the removing of final
    {
        Console.WriteLine("InheritanceB - One");
    }
}

class NewInherit : InheritanceTesting
{
    public new void InheritanceOne() 
    // There is no Java equivalent to this statement
    {
        Console.WriteLine("InheritanceA - Two");
    }


    public override void InheritanceB()
    // Java equivalent would be
    // public void InheritanceB()
    {
        Console.WriteLine("InheritanceB - Two");
    }
}

您所看到的是 C# 和 Java 之间的一些区别,您可以让 C# 表现得像 Java,就像 InheritanceB 将显示的方法一样。

默认情况下,C# 方法是最终方法,因此您需要采取积极措施,通过将方法标记为虚拟方法来覆盖该方法。因此,虚方法 InheranceB 的行为将与您期望的方法行为一样,方法分派基于对象类型,而不是引用类型。例如

 NewInherit example = new NewInherit();
 InheritanceTesting secondReference = example;
 example.InheritanceB();
 secondreference.InheritanceB();

将产生InheritanceB - Two,因为方法 InheritanceB 是虚拟的(能够被覆盖)和被覆盖(使用覆盖方法)。

您所看到的称为方法隐藏,其中方法不能被覆盖(非虚拟)但可以隐藏,隐藏方法仅在引用(不是对象)是派生类型时才会隐藏,所以

 NewInherit example = new NewInherit();
 InheritanceTesting secondReference = example;
 example.InheritanceA();
 secondreference.InheritanceA();

将首先产生InheritanceB - Two,然后产生InheritanceB - One。这是因为(至少在简单的情况下)最终方法的调用是在编译时根据引用类型绑定的。这具有性能优势。虚拟方法的绑定需要与运行时不同,因为编译器可能不知道实例类。

在实践中方法隐藏并没有被广泛使用,一些组织有禁止它的编码标准。通常的做法是将您希望子类能够覆盖的方法标记为virtual,并在子类中使用关键字override


更直接地回答您的问题

这是否意味着 C# 中的情况正好相反?仅对象 “知道”它声明的类型?

不,c# 知道构造类型(实例)和声明类型(引用)。即使实例隐藏了方法,它也将实例类型用于覆盖的方法,将声明的类型用于最终方法。

如果是这样,其中的逻辑/优势是什么?

不是这样,我相信在可能的情况下在编译时绑定有性能优势,例如允许内联方法。此外,正如所解释的那样,不会失去灵活性,因为您可以通过使用 virtualoverride 关键字来获得与 Java 相同的行为。

【讨论】:

  • 等一下,我会放弃我的答案并为你的答案投票。 +1 用于在声明中详细了解 new
  • 哇!非常感谢,这真的很清楚。 :)
【解决方案2】:

Java 默认将方法视为虚拟方法,C# 方法默认为非虚拟方法。如果您希望在 C# 中具有相同的行为,请使用 virtual 关键字。在 Java 中,您可以使用 final 来确保继承的类不会覆盖方法。

默认情况下 C# 方法不是虚拟的原因很可能会阻止人们以基类设计者不打算的方式动态更改每个继承函数的行为。这为基类设计者提供了更多控制权,并确保对继承进行仔细和有意的计划,而不是即时完成。

【讨论】:

  • 这解释了行为。添加一些细节以改进您的答案。
  • @helb OP 表达他的问题的方式我认为他只需要用正确的术语稍微推动一下,并意识到这只是默认差异,而不是类之间实际工作方式的巨大差异语言。我确实根据您的要求添加了更多详细信息。
  • 感谢您指出这一点:“认识到这只是默认差异,而不是在语言之间的类实际工作方式上存在巨大差异”。
【解决方案3】:

Java 默认使方法为虚拟,而在 C# 中,您必须显式启用虚拟继承。

这就是你添加 new 修饰符的原因,对吧?因为你收到了警告?那是因为如果没有虚拟方法,如果在派生类中重新定义它,则静态替换它。如果不是通过基指针调用InheritanceOne(),而是通过派生指针调用它,您将得到您期望的结果——编译器在编译时选择非虚拟方法,仅基于编译时信息。

TL;DR: 任何时候你想对一个方法使用继承,在 C# 中让它成为虚拟的。 new 是语言中最糟糕的方法之一,它没有真正的用途,只会在你的代码中添加陷阱。

【讨论】:

  • 我不确定我是否同意新的“没有实际用途”。它有有效的用例,没有它是不可能的。尽管我同意那些只是试图让编译器满意的语言新手经常错误地使用它。
  • 有哪些用例不能通过正确命名相关方法而变得更好?
  • @Blindy new 的场景是我在一个类上有一个公共方法,该类在另一个我无法控制的程序集中有一个基类。我更新到这个程序集的 v2,当我重新编译时,我发现一个方法被添加到我的方法隐藏的基类中(因为 C# 给了我一个警告)。我不能更改名称,因为它是公开的,所以 new 用于抑制它。 Java 行为在版本控制方面更加脆弱。我可以轻松地重写一个我不打算重写的方法,只需将它添加到基类中即可。
  • @mike z,当然,如果您曾经通过指向您的封闭源代码基类的指针使用该类,那么一切都会崩溃。这包括将使用你的类的每一个人。
  • @Blindy 我并不是说使用任何一种语言都很好。我的意思是,发生无意方法隐藏(或在 Java 中覆盖)的一种常见方法是修改基类以添加它不知道的派生类中存在的方法。 “正确命名有问题的方法”并不能解决这个问题。
【解决方案4】:

你可能认为你写了同样的东西(同样的词),虽然你没有(你在 c# 版本中使用了 new 关键字),但 C# 相当于你用 Java 写的东西。

class InheritanceTesting
{
    public virtual void InheritanceOne()
    {
        Console.WriteLine("InheritanceOne");
    }
}

class NewInherit : InheritanceTesting 
{
    public override void InheritanceOne()
    {
        Console.WriteLine("InheritanceTwo");
    } 
 }

在 Java 中,默认情况下,除了privates 和statics 之外的所有方法都是virtual,任何与超类方法具有相同签名的方法都是override

【讨论】:

    【解决方案5】:

    默认情况下,Java 中的每个方法都可以被其子类覆盖(除非私有/静态等)。

    在 C# 中,如果方法必须被子类覆盖,则必须将其设为虚拟方法。

    在您的 C# 示例中,它不是被覆盖的方法,因此该行为是预期的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-04-18
      • 2010-11-13
      • 1970-01-01
      • 1970-01-01
      • 2013-02-18
      • 1970-01-01
      • 2011-09-14
      • 1970-01-01
      相关资源
      最近更新 更多