Answers:
应该为继承设计类,以便能够利用它。virtual
默认情况下拥有方法意味着可以将类中的每个函数插入并替换为另一个函数,这并不是一件好事。许多人甚至认为班级应该是sealed
默认设置。
virtual
方法也可能有轻微的性能影响。但是,这可能不是主要原因。
callvirt
容易call
地找到用IL代码中的简单内容替换的位置(或者在JITting时甚至更下游)。Java HotSpot也是如此。其余的答案就在眼前。
我感到惊讶的是,这里似乎达成了这样的共识,即默认非虚拟化是正确的处理方式。我要讲的是栅栏的另一面-我认为是实用的。
多数论据都像古老的“如果我们给您力量,您可能会伤害自己”这样的论点读给我听。来自程序员吗?
在我看来,像编码员一样,我不了解(或没有足够的时间)来设计自己的库以进行继承和/或可扩展性,是恰好产生了我可能必须修复或调整的库的编码员。覆盖功能最有用的库。
我不得不编写丑陋,绝望的变通方法代码(或者放弃使用并推出自己的替代解决方案)的次数,因为我无法覆盖太多,远远超过了我被咬过的次数(例如在Java中),以覆盖设计者可能没有考虑过的地方。
默认非虚拟使我的生活更加艰难。
更新: [正确地指出]我实际上没有回答这个问题。所以-很抱歉迟到了....
我有点想写一些像“ C#在默认情况下将方法实现为非虚拟方法,因为做出了一个错误的决定,使程序对程序的重视程度高于程序员”这样的东西。(我认为,根据该问题的其他一些答案(例如性能(过早的优化,有人吗?)或保证类的行为),这在某种程度上是合理的。)
但是,我意识到我只是在陈述自己的观点,而不是Stack Overflow想要的明确答案。当然,我认为,在最高层次上,确定的(但无济于事的)答案是:
默认情况下,它们是非虚拟的,因为语言设计者需要做出决定,而这正是他们选择的。
现在我想他们做出该决定的确切原因我们永远不会....噢,等等! 对话记录!
因此,这里关于覆盖API的危险和显式设计继承的需求的答案和评论似乎在正确的轨道上,但是都缺少一个重要的时间方面:Anders的主要关注点是维护类或API的隐式跨版本合同。而且我认为他实际上更关心的是允许.Net / C#平台在代码下进行更改,而不是关注平台顶部的用户代码更改。(他的“务实”观点与我完全相反,因为他是从另一侧看的。)
(但是他们不能只是默认情况下选择了虚函数,然后在代码库中添加了“最终”字吗?也许那是不完全一样的。而且安德斯显然比我聪明,所以我要撒谎。)
virtual
... Visual Studio加载项,有人吗?
总结一下别人说的话,有以下几个原因:
1-在C#中,语法和语义上有很多东西直接来自C ++。C ++在默认情况下不是虚拟的方法会影响C#。
2-默认情况下,将每个方法虚拟化是性能问题,因为每个方法调用都必须使用对象的虚拟表。此外,这严重限制了即时编译器内联方法和执行其他类型的优化的能力。
3-最重要的是,如果默认情况下方法不是虚拟的,则可以保证类的行为。当它们默认为虚拟时(例如在Java中),您甚至无法保证简单的getter方法会按预期进行,因为它可能会被覆盖以在派生类中执行任何操作(当然,您可以并且应该使方法和/或最终课程)。
正如Zifre所提到的,您可能会想知道为什么C#语言没有更进一步,并且默认情况下将类密封。这是关于实现继承问题的整个辩论的一部分,这是一个非常有趣的话题。
C#受C ++(及更多)的影响。默认情况下,C ++不启用动态分派(虚拟函数)。一个(好的?)论点是一个问题:“您多久实现一次属于类的成员的类?”。默认情况下避免启用动态调度的另一个原因是内存占用量。当然,没有指向虚拟表的虚拟指针(vpointer)的类比启用了后期绑定的相应类要小。
性能问题并不是那么容易说“是”或“否”。这样做的原因是Just In Time(JIT)编译,它是C#中的运行时优化。
关于“ 虚拟通话速度.. ”的另一个类似问题
原因很简单,除了性能成本外,还有设计和维护成本。与非虚拟方法相比,虚拟方法具有额外的成本,因为该类的设计者必须计划该方法被另一个类覆盖时发生的情况。如果您期望特定的方法更新内部状态或具有特定的行为,则这会产生很大的影响。现在,您必须计划派生类更改该行为时会发生什么。在这种情况下,编写可靠的代码要困难得多。
使用非虚拟方法,您可以完全控制。出问题的是原始作者的错。该代码更容易推理。
来自Perl的背景,我认为C#密封了每个可能想要通过非虚拟方法扩展和修改基类行为的开发人员的厄运,而这并没有迫使新类的所有用户都意识到潜在的隐患。细节。
考虑List类的Add方法。如果开发人员想要在“添加”特定列表时更新多个潜在数据库之一,该怎么办?如果默认情况下'Add'是虚拟的,则开发人员可以开发一个'BackedList'类来覆盖'Add'方法,而不必强迫所有客户端代码都知道它是'BackedList'而不是常规的'List'。出于所有实际目的,可以将“ BackedList”视为客户端代码中的另一个“ List”。
从大型主类的角度来看,这很有意义,该主类可能提供对一个或多个列表组件的访问,这些组件本身由数据库中的一个或多个模式支持。鉴于默认情况下C#方法不是虚拟的,所以由主类提供的列表不能是简单的IEnumerable或ICollection,甚至不能是List实例,而必须改为以“ BackedList”的形式发布给客户端,以确保新版本调用“添加”操作中的“更新”以更新正确的架构。
B
is为时才应使用继承模型A
。如果B
需要与之不同的东西A
则不是A
。我认为,将语言作为一种功能来覆盖是设计缺陷。如果您需要其他Add
方法,则您的集合类不是List
。试图说这是假的。正确的方法是组合(而不是伪造)。没错,整个框架都建立在最重要的功能之上,但是我只是不喜欢它。
这当然不是性能问题。Sun的Java解释器使用相同的代码进行调度(invokevirtual
字节码),而HotSpot则生成完全相同的代码final
。我相信所有C#对象(但不是结构)都具有虚拟方法,因此您总是需要vtbl
/ runtime类标识。C#是“类Java语言”的方言。暗示它来自C ++并不完全诚实。
有一个想法,您应该“为继承而设计或禁止继承”。直到您要解决一个严重的业务案例时,这听起来都是个好主意。也许从您无法控制的代码继承。
final
有效final
方法之间的区别是微不足道的。还值得注意的是,它可以进行双态内联,即具有两种不同实现的内联方法。
this
引用为null,则不可能有行为不同的方法。有关更多信息,请参见此处。