为什么C#默认将方法实现为非虚方法?

Bur*_*gan 105 .net c# java virtual-functions

与Java不同,为什么C#默认将方法视为非虚函数?它更可能是性能问题而不是其他可能的结果吗?

我想起了Anders Hejlsberg的段落,内容是现有架构带来的几个优点.但是,副作用呢?默认情况下使用非虚方法真的是一个很好的权衡吗?

Meh*_*ari 98

应该为继承设计类以便能够利用它.其方法virtual在默认情况下意味着,在每类功能,可以插入和更换另一个,这是不是一个真正的好东西.很多人甚至认为课程应该是sealed默认的.

virtual方法也可能有轻微的性能影响.然而,这可能不是主要原因.

  • 密封课程让我想要打宝宝. (71认同)
  • 许多人也是精神病患者,并不意味着我们应该倾听他们.虚拟应该是C#中的默认值,代码应该是可扩展的,无需更改实现或完全重写.过度保护API的人通常最终会使用死的或半使用的API,这比滥用它的情况要糟糕得多. (49认同)
  • 就个人而言,我怀疑这部分是关于表现的.即使对于虚函数,编译器也能够通过IL代码中的简单`call`(或者在JITting时更进一步的下游)找出替换`callvirt`的位置.Java HotSpot也是如此.答案的其余部分是正确的. (7认同)
  • @mxmissile:我也是,但无论如何,好的API设计都很难.我认为默认密封对于您拥有的代码非常有意义.通常情况下,团队中的其他程序员_did不会在他们实现您需要的类时考虑继承,并且默认密封会很好地传达该消息. (6认同)
  • 我同意性能不是最重要的,但它*也可以*具有性能影响.它可能非常小.当然,这不是*这个选择的原因*.只是想提一下. (5认同)
  • 这给了我很大的痛苦,我想重写一个简单的属性来做一些额外的东西(当然,我可以写一个新的方法 - 但原来的基类没有那个方法,所以它没有''因为代码在许多列表中使用多态性,所以工作得很好.信任程序员将正确的东西标记为虚拟对于影响最终用户的库有影响. (4认同)
  • 我想看看这个问题/答案由Java设计师(Josh Bloch&Co.)审查.他们会说什么? (3认同)
  • "密封"和"虚拟"不是唯一的选择."密封"意味着,"我想到了设计,你不能延长它." "虚拟"意味着,"我考虑过设计,你可以扩展它." 没有"密封"或"虚拟"意味着,"我没有考虑设计,所以请自行承担风险." 你可以用C#覆盖任何东西,但有些东西需要比其他东西更多的障碍.扩展一个不是用于多态的类是一种破解,在任何语言中,C#只是让你承认它. (3认同)
  • 考虑到有多少框架和方法需要虚拟属性和方法(许多ORM,模拟框架,测试套件)被认为是"最佳实践"或现代的审查模式,C#似乎肯定走错了道路. (2认同)
  • @notfed这就是你定义界面的原因.如果API开发人员没有,您可以使用真实包装器轻松创建自己的.老实说,这并不难.我也想知道为什么这么多人抱怨密封课程; 让我觉得人们太快认为最好的答案就是遗传很少(有利于构成而非继承,对吧?). (2认同)

mwa*_*rdm 89

我很惊讶这里似乎有这样的共识,即非虚拟默认是正确的做事方式.我会在另一方面下台 - 我认为实用主义的一面.

大多数理由都像旧的"如果我们给你力量,你可能会伤到自己"这样的论点.来自程序员?!

在我看来,编码人员不够了解(或有足够的时间)设计他们的库以进行继承和/或可扩展性的编码器是完全生成我可能需要修复或调整的库 - 完全是库中的覆盖能力最有用.

我必须编写丑陋,绝望的解决方案代码(或放弃使用并推出我自己的替代解决方案)的次数,因为我无法超越,远远超过我被咬过的次数(例如在Java中)通过覆盖设计者可能没有考虑过的地方.

非虚拟默认让我的生活更加困难.

更新:有人指出[非常正确]我实际上没有回答这个问题.所以 - 并为迟到而道歉......

我有点想写一些简洁的东西,比如"C#默认情况下将方法实现为非虚拟,因为做出了一个错误的决定,它比程序员更重视程序".(我认为根据这个问题的其他一些答案 - 比如性能(过早优化,任何人?),或保证类的行为,这可能有点合理.)

但是,我意识到我只是在陈述我的意见,而不是Stack Overflow所希望的那个明确的答案.当然,我认为,在最高级别,最终(但没有帮助)的答案是:

默认情况下,它们是非虚拟的,因为语言设计者决定制作,这就是他们所选择的.

现在我想他们做出决定的确切原因我们永远不会......哦,等等! 谈话的成绩单!

因此,似乎这里关于重写API的危险和明确设计继承的必要性的答案和评论都在正确的轨道上,但都缺少一个重要的时间方面:Anders的主要关注点是维护一个类或API的隐含跨版本合同.而且我认为他实际上更关心的是允许.Net/C#平台在代码下进行更改,而不是担心平台上的用户代码更改.(而他的"务实"观点与我的完全相反,因为他从另一边看.)

(但他们不能只选择虚拟默认,然后通过代码库填写"最终"吗?也许这并不完全相同......而Anders显然比我聪明,所以我会让它撒谎.)

  • 我热情地同意.如果您要发布API(无论是公司内部还是外部世界),您确实可以并且应该确保您的代码是为继承而设计的.如果您要将API发布给许多人使用,那么您应该使API变得更好.与良好的整体设计(良好的内容,清晰的用例,测试,文档)所需的努力相比,继承的设计确实不是太糟糕.如果你不发布,默认情况下虚拟化的时间会更短,而且你可以随时修复一小部分有问题的案例. (3认同)
  • 完全同意.在使用第三方API并希望覆盖某些行为而无法执行时非常令人沮丧. (2认同)
  • 现在,如果Visual Studio中只有一个编辑器功能,则将所有方法/属性自动标记为"virtual"... Visual Studio加载项是谁? (2认同)
  • 考虑到依赖注入,模拟框架和ORM等现代开发实践,我们的C#设计人员似乎很清楚这一点.如果默认情况下无法覆盖属性,则必须测试依赖项非常令人沮丧. (2认同)

Zif*_*fre 17

因为很容易忘记一个方法可能被覆盖而不是为此设计.C#让你在虚拟之前思考.我认为这是一个伟大的设计决定.有些人(比如Jon Skeet)甚至说过应该默认密封课程.


Tri*_*ian 12

总结其他人所说的,有几个原因:

1-在C#中,语法和语义有很多东西直接来自C++.事实上,C++中默认情况下不是虚拟的方法影响了C#.

2-默认情况下,每个方法都是虚拟的,这是一个性能问题,因为每个方法调用都必须使用对象的虚拟表.此外,这极大地限制了Just-In-Time编译器内联方法和执行其他类型优化的能力.

3-最重要的是,如果默认情况下方法不是虚拟的,则可以保证类的行为.当它们在默认情况下是虚拟的时,例如在Java中,你甚至不能保证简单的getter方法会按预期执行,因为它可以被覆盖以在派生类中执行任何操作(当然,你可以而且应该,方法和/或类最终).

正如Zifre所提到的,人们可能会想,为什么C#语言没有更进一步,默认情况下会使类密封.这是关于实现继承问题的整个辩论的一部分,这是一个非常有趣的话题.


Sch*_*jer 9

C#受C++(及更多)的影响.默认情况下,C++不启用动态分派(虚拟功能).对此的一个(好的?)论点是这样一个问题:"你多久实现一个属于类层次结构成员的类?".默认情况下避免启用动态分派的另一个原因是内存占用.没有指向虚拟表的虚拟指针(vpointer)的类比使用后期绑定的相应类小.

性能问题并不容易说"是"或"不".原因是Just In Time(JIT)编译,它是C#中的运行时优化.

关于" 虚拟通话速度...... "的另一个类似问题


Jar*_*Par 5

除了性能成本之外,简单的原因是设计和维护成本.与非虚方法相比,虚方法具有额外的成本,因为类的设计者必须计划当方法被另一个类重写时发生的事情.如果您希望特定方法更新内部状态或具有特定行为,则会产生很大影响.您现在必须计划派生类更改该行为时会发生什么.在那种情况下编写可靠的代码要困难得多.

使用非虚拟方法,您可以完全控制.任何出错的都是原作者的错.代码更容易推理.