gpt4 book ai didi

c# - 继承基类时,C#是否默认为new而不是override是有原因的吗?

转载 作者:行者123 更新时间:2023-11-30 13:44:01 25 4
gpt4 key购买 nike

我知道重写和新方法之间的区别(或者仍然相信这样做),并且有几个问题描述了两者之间的区别,但是我的问题是C#是否默认为新行为(带有警告)是否有特定原因),而不是默认覆盖?

public class Base
{
virtual public string GetString() => "Hello from Base";
}

public class Child : Base
{
public string GetString() => "Hello from Child";
}

...

var childAsBase = (Base)new Child();
Console.WriteLine(childAsBase.GetString());

...

c:\>dotnet run
child.cs(5,23): warning CS0114: 'Child.GetString()' hides inherited member 'Base.GetString()'.
To make the current member override that implementation, add the override keyword.
Otherwise add the new keyword. [C:\IPutAllMyProjectsInMyRootFolder.csproj]
Hello from Base


我可以认为,无论继承的方法是否标记为虚拟,都可以得到相同的行为,但是同时,将其声明为虚拟是在说“重写我”,因此默认情况下重写对我来说是合理的。

我不介意的另一个原因是使用虚拟函数表的成本,但是就我作为编码人员的要求代码要比保存cpu周期更重要的意义而言,这似乎是一个令人恐惧的原因。但是也许是当语言被发明时,事实并非如此吗?

最佳答案

当涉及类型层次结构的C#语言设计决策对您来说很不常见时,一种好技巧是问自己一个问题:“如果有人不告诉我就更改了我的基类,将会发生什么?” C#是经过精心设计的,目的是减轻脆性基类故障的成本,这就是其中之一。

我们首先考虑阴影方法具有override关键字的情况。

这向编译器指示派生类作者和基类作者正在合作。基类作者创建了一个可重写的方法,这是一件非常危险的事情。可重写的方法意味着您无法编写测试该方法所有可能行为的测试用例!必须设计一种方法的可重写性,因此您必须说一种方法是虚拟的(或抽象的)。

如果我们看到一个override修饰符,则说明基类和派生类作者都对该危险扩展点的正确性和安全性负责,并已成功地相互沟通以就合同达成协议。

接下来,让我们考虑阴影方法具有new关键字的情况。再一次,我们知道派生类作者已经检查了基类,并确定带阴影的方法(无论是否是虚拟的)都不能满足派生类消费者的需求,并且故意做出了危险的决定以使用两种方法具有相同的签名。

这就给我们留下了阴影方法既没有override也没有new的情况。我们没有证据表明派生类的作者知道基类中的方法。实际上,我们有相反的证据。如果他们知道虚拟基类方法,则将其重写以匹配虚拟方法的约定,如果他们知道非虚拟基类方法,则将故意做出危险的决定以使其模糊。

这种情况怎么会出现?我想到的只有两种方法。

首先,派生类作者对他们的基类没有足够的研究,并且不知道他们刚刚使用的方法的存在,这是一个可怕的位置。派生类继承了基类的行为,可以使用在需要维护基类不变式的情况下!我们必须警告无知的开发人员,他们正在做极其危险的事情。

其次,在对基类进行更改之后,将重新编译派生类。现在,派生类的作者并不知道基类,因为它是原始编写的,在他们设计派生类时,以及在测试派生类时。但是他们不知道基类已经改变的事实。

同样,我们必须警告无知的开发人员发生了一些事情,他们需要做出重要的决定:尽可能覆盖,确认隐藏,重命名或删除派生的类方法。

这样就证明了为什么在阴影方法未标记为newoverride时必须发出警告的理由。但这不是你的问题。您的问题是“为什么默认为new?”

好吧,假设您是编译器开发人员。当编译器遇到缺少newoverride的阴影方法时,可以选择以下方法:


没做什么;不要给出警告或错误,并选择一种行为。如果代码由于脆性基类故障而中断,那就太糟糕了。您应该更仔细地研究基类。显然,我们可以做得更好。
使其成为错误。现在,基类作者可以通过更改基类的成员来破坏您的构建。这不是一个糟糕的主意,但是我们现在必须权衡所需的构建中断的成本-因为他们发现了一个错误-与不需要的构建中断的成本-需要默认行为的情况-与忽略构建成本的开销意外警告并引入错误。


这是一个棘手的电话,各方都有争论。引入警告是一个合理的妥协立场;您可以始终打开“警告是错误”,我建议您这样做。


发出警告,如果基本方法可覆盖,则将其覆盖;如果基本方法不可覆盖,则将其覆盖。这不仅不一致,而且我们刚刚引入了另一种脆弱的基类故障。你看到了吗?如果基类作者将其方法从非虚拟方法更改为虚拟方法,反之亦然呢?这将导致意外遮蔽方法从覆盖更改为阴影,反之亦然。


但是,暂时暂时将其搁置一旁。如果可能,自动覆盖的其他后果是什么?请记住,该场景的前提是重写是偶然的,派生类的作者不了解基类的实现细节,不变式和公共表面积。

与仅更改通过派生类型的接收器调用阴影方法的那些调用者的行为的危险相比,自动更改基类方法的所有调用者的行为似乎具有疯狂的危险。


发出警告,默认为阴影而不是覆盖。通常,此选择更安全,它避免了第二种脆弱的基故障,避免了构建中断,具有基类接收器的方法的调用者获得了其测试用例所期望的行为,而具有派生类接收器的方法的调用者获得了测试用例所期望的行为。他们期望的行为。


所有设计选择都是仔细权衡许多互不兼容的设计目标的结果。 C#的设计人员特别关注大型团队,他们正在研究版本化的软件组件,在这些组件中,基类可能会以意想不到的方式进行更改,并且团队之间可能无法很好地交流这些更改。


  我不介意的另一个原因是使用虚拟函数表的成本,但是就我作为编码人员的要求代码要比保存cpu周期更重要的意义而言,这似乎是一个令人恐惧的原因。但是也许是当语言被发明时,事实并非如此吗?


虚拟方法会带来成本;显而易见的代价是在运行时需要进行额外的表跳转,并需要获得所需的代码。还有一些不太明显的成本,例如:抖动无法内联对非密封方法的虚拟调用,等等。

但是,正如您所指出的,将非虚拟设置为默认值的原因并非主要是为了提高性能。主要原因是虚拟化非常危险,需要谨慎设计。必须记录和传达必须由覆盖方法的派生类维护的不变式。正确设计类型层次结构是昂贵的,并且选择加入可以降低成本并提高安全性。坦白说,我也希望默认也是密封的。

关于c# - 继承基类时,C#是否默认为new而不是override是有原因的吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46793770/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com