在继承基类时,有没有理由为什么C#默认为new而不是覆盖?
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了在继承基类时,有没有理由为什么C#默认为new而不是覆盖?,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含4795字,纯文字阅读大概需要7分钟。
内容图文
![在继承基类时,有没有理由为什么C#默认为new而不是覆盖?](/upload/InfoBanner/zyjiaocheng/754/078af55d3065441e8753909c9b22e498.jpg)
我知道覆盖和新的区别(或者相信这样做无论如何),并且有几个问题描述了两者之间的差异,但我的问题是,是否有一个特殊的原因,为什么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-cycle更重要.但也许回到语言发明时,情况并非如此?
解决方法:
当涉及类型层次结构的C#语言设计决策对您而言似乎不寻常时,一个好的技巧就是问自己“如果有人在没有告诉我的情况下改变我的基类会怎么样?” C#经过精心设计,可以降低脆弱的基类故障的成本,这就是其中之一.
让我们首先考虑一个阴影方法有override关键字的情况.
这向编译器指示派生类作者和基类作者是合作的.基类作者制作了一个可覆盖的方法,这是一个非常危险的事情.可覆盖的方法意味着您不能编写测试用例来测试该方法的所有可能行为!必须设计方法的可覆盖性,因此您需要说方法是虚拟的(或抽象的).
如果我们看到一个覆盖修饰符,那么我们就知道基类和派生类作者都对这个危险的扩展点的正确性和安全性负责,并且已经成功地相互沟通以商定合同.
让我们接下来考虑阴影方法具有new关键字的情况.同样,我们知道派生类作者已经检查了基类,并确定了阴影方法,无论是否为虚拟方法,都不能满足派生类消费者的需求,并故意做出危险的决定,有两种方法具有相同签名的.
然后,我们留下了阴影方法既没有覆盖也没有新的情况.我们没有证据表明派生类的作者知道基类中的方法.事实上,我们有相反的证据;如果他们知道虚拟基类方法,他们会覆盖它以匹配虚方法的契约,如果他们知道非虚基类方法,那么他们会故意做出危险决定来影响它.
这种情况怎么会出现?只有两种方式可以想到.
首先,派生类作者没有充分研究他们的基类,并且不知道他们刚刚隐藏的方法的存在,这是一个可怕的位置.派生类继承了基类的行为并且可以使用在需要维护基类的不变量的场景中!我们必须警告无知的开发人员他们正在做一些非常危险的事情.
其次,在更改基类之后重新编译派生类.现在,派生类作者并不是因为它是原始编写的基类而无知,并且因为他们设计了他们的派生类,并且他们测试了他们的派生类.但他们不知道基类发生了变化.
同样,我们必须警告无知的开发人员发生了一些事情,他们需要做出以下重要决定:尽可能覆盖,或者确认隐藏,或重命名或删除派生类方法.
这样就证明了为什么在阴影方法既不标记也不重写时必须给出警告.但这不是你的问题.你的问题是“为什么要默认新的?”
好吧,假设您是编译器开发人员.当编译器面临缺少new和覆盖的阴影方法时,以下是您的选择:
>什么都不做;不发出任何警告或错误,并选择一种行为.如果代码由于脆弱的基类失败而中断,那就太糟糕了.您应该更仔细地查看基类.显然,我们可以做得比这更好.
>使它成为一个错误.现在,基类作者可以通过更改基类的成员来破坏您的构建.这不是一个糟糕的想法,但我们现在必须权衡所需的构建中断的成本 – 因为他们发现了一个错误 – 针对不需要的构建中断的成本 – 需要默认行为 – 而忽略了意外警告并引入错误.
这是一个棘手的问题,各方都有争论.引入警告是一个合理的妥协立场;你可以随时打开“警告是错误”,我建议你这样做.
>使其成为警告,并在基本方法可覆盖时使其覆盖,如果基本方法不可覆盖则使其覆盖.这不仅是不一致的,而且我们刚刚引入了另一种脆弱的基类失败.你看到了吗?如果基类作者将其方法从非虚拟更改为虚拟,反之亦然,该怎么办?这会导致意外阴影方法从覆盖变为阴影,反之亦然.
但是让我们暂时搁置一边.如果可能,自动覆盖的其他后果是什么?请记住,场景的前提是覆盖是偶然的,派生类作者不了解基类的实现细节,不变量和公共表面区域.
与仅通过派生类型的接收方调用阴影方法的那些调用者的行为的改变的危险相比,自动改变基类方法的所有调用者的行为似乎是非常危险的.
>使其成为警告,默认为阴影,而不是覆盖.这种选择一般更安全,它避免了第二种脆弱的基础故障,它避免了构建中断,基类接收器的方法的调用者获得了他们的测试用例期望的行为,并且具有派生类接收器的方法的调用者得到了他们期望的行为.
所有设计选择都是仔细权衡许多互不相容的设计目标的结果. C#的设计者特别关注从事版本化软件组件的大型团队,其中基类可能以意想不到的方式发生变化,团队可能无法将这些变化很好地传达给彼此.
Another reason that crossed my mind is the cost of using a virtual function table, but that seems like a scary reason in the sense that what I as a coder want the code to do should be more important than saving cpu-cycles. But perhaps back when the language was invented, that was not the case?
虚拟方法引入成本;显而易见的成本是运行时额外的表跳转以及获取它所需的代码.还有一些不太明显的成本:抖动不能内联虚拟调用非密封方法,等等.
但正如您所注意到的,将非虚拟化作为默认值的原因主要不是为了提高性能.主要原因是虚拟化非常危险,需要仔细设计.必须由覆盖方法的派生类维护的不变量需要记录和传达.正确设计类型层次结构是昂贵的,并使其选择性地降低成本并提高安全性.坦率地说,我希望密封也是默认的.
内容总结
以上是互联网集市为您收集整理的在继承基类时,有没有理由为什么C#默认为new而不是覆盖?全部内容,希望文章能够帮你解决在继承基类时,有没有理由为什么C#默认为new而不是覆盖?所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。