隐藏在 C# 和 Java 中的基本方法

Base method hiding in C# and Java

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

在 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 中被教导 "object knows what type it is instantiated to",因此,当我调用被覆盖的方法时并不奇怪。这是否意味着在 C# 中情况正好相反?仅对象 "knows" 其声明的类型?如果是这样,那里面的 logic/advantage 是什么?在我看来,Java 将基础 classes 视为接口 - 这是您的类型,这是您的实际实现。我是 C# 的新手,也许我在这里遗漏了一些明显的东西?

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

C# 方法在默认情况下不是虚拟的,这很可能是为了防止人们能够以基本 class 设计者不希望的方式即时更改每个继承函数的行为。这为基础 class 设计人员提供了更多控制权,并确保继承是经过仔细和有意计划的,而不是临时完成的。

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

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

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

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

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# 方法是最终方法,因此您需要采取积极措施,通过将方法标记为虚拟方法来重写该方法。因此,虚方法 InheratanceB 的行为将与您期望的方法一样,方法分派基于对象类型,而不是引用类型。例如

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

由于方法 InheritanceB 是虚拟的(能够被覆盖)和被覆盖(使用覆盖方法),因此两者都会产生 InheritanceB - Two

你在哪里看到的叫做方法隐藏,其中方法不能被覆盖(非虚拟)但可以隐藏,隐藏方法只有在引用(而不是对象)是派生类型时才会隐藏所以

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

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

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


更直接地回答您的问题

Does this mean that the situation is the opposite in C#? Object only "knows" its declared type?

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

If so, what is the logic/advantage in that?

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

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

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

TL;DR:只要您想对方法使用继承,请在 C# 中将其设为虚拟。 new 是语言中最糟糕的方法之一,它没有实际用途,只会给您的代码添加陷阱。

你可能认为你写了同样的东西(相同的词),虽然你没有(你在 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 并且任何方法都具有与超级 [ 相同的签名=22=] 方法是一个 override.