我来自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#的新手,也许我错过了一些明显的东西?
答案 0 :(得分:3)
稍微有趣的一点是以下
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#方法是final,因此您需要采取积极的操作,以便通过将方法标记为虚拟来覆盖方法。因此,虚拟方法InheratanceB的行为与您期望的方法一样,基于对象类型而不是引用类型进行方法分派。 e.g。
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#知道构造的类型(实例)和声明的类型(引用)。它使用实例类型用于重写方法,并使用最终方法的声明类型,即使实例隐藏了该方法。
如果是这样,那是什么逻辑/优势?
不是这样,我相信在可能的情况下,在编译时绑定会有性能优势,例如:允许内嵌方法。此外,正如所解释的那样,不会失去灵活性,因为您可以使用virtual
和override
关键字与Java具有相同的行为。
答案 1 :(得分:2)
Java默认将方法视为虚拟,默认情况下C#方法是非虚拟的。如果您想在C#中使用相同的行为,请使用virtual关键字。在Java中,您可以使用final来确保继承的类不会覆盖方法。
默认情况下,C#方法不是虚拟的原因最有可能阻止人们以基类设计者不想要的方式动态更改每个继承的函数行为。这样可以更好地控制基类设计器,并确保继承是经过精心策划的,而不是仅仅是动态完成。
答案 2 :(得分:1)
Java默认情况下使方法成为虚拟,而在C#中,您必须显式启用虚拟继承。
这就是你添加new
修饰符的原因吗?因为你有关于它的警告?那是因为没有方法是虚拟的,如果在派生类中重新定义它,则将其替换为静态。如果不通过基类指针调用InheritanceOne()
而是通过派生指针调用它,您将得到预期的结果 - 编译器在编译时根据编译时信息选择非虚方法。
TL; DR:只要您想为方法使用继承,请在C#中将其设为虚拟。 new
是方法语言中最糟糕的事情之一,它没有实际用途,只会为你的代码添加陷阱。
答案 3 :(得分:1)
你可能认为你写了相同的东西(相同的单词),虽然你没有(你在c#版本中使用了new
关键字),但你用Java编写的C#等价于此。
class InheritanceTesting
{
public virtual void InheritanceOne()
{
Console.WriteLine("InheritanceOne");
}
}
class NewInherit : InheritanceTesting
{
public override void InheritanceOne()
{
Console.WriteLine("InheritanceTwo");
}
}
在Java中,默认情况下,除private
和static
之外的所有方法都是virtual
,并且任何方法与超类方法具有相同的签名是{{1 }}
答案 4 :(得分:0)
默认情况下,Java中的每个方法都可以被子类覆盖(除非私有/静态等)。
在C#中,如果必须通过子类覆盖,则必须使方法成为虚拟方法。
在您的C#示例中,它不是覆盖方法,因此行为是预期的。