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

时间:2009-05-02 14:28:05

标签: c# java .net virtual-functions

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

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

9 个答案:

答案 0 :(得分:97)

类应该设计以便继承,以便能够利用它。默认情况下使用方法virtual意味着可以插入类中的每个函数并替换为另一个函数,这实际上并不是一件好事。很多人甚至认为默认情况下课程应该是sealed

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

答案 1 :(得分:87)

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

大多数理由都像旧的“如果我们给你力量,你可能会伤害自己”这个论点。来自程序员?!

在我看来,编码人员如果没有足够的知识(或有足够的时间)来设计他们的库以用于继承和/或可扩展性,那么编码器就是编写了我可能需要修复或调整的库。 - 恰好是能够覆盖的库最有用的库。

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

非虚拟默认让我的生活更加艰难。

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

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

然而,我意识到我只是在陈述我的观点而不是Stack Overflow所希望的那个明确的答案。当然,我认为,在最高级别,最终(但无益)答案是:

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

现在我想他们做出决定的确切原因我们永远不会......哦,等等! The transcript of a conversation!

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

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

答案 2 :(得分:17)

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

答案 3 :(得分:12)

总结别人说的话,有几个原因:

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

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

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

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

答案 4 :(得分:9)

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

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

关于“speed of virtual calls..”的另一个类似问题

答案 5 :(得分:5)

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

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

答案 6 :(得分:2)

如果所有C#方法都是虚拟的,那么vtbl会更大。

如果类定义了虚拟方法,则C#对象只有虚方法。确实,所有对象都具有包含vtbl等价物的类型信息,但是如果没有定义虚拟方法,则只存在基础Object方法。

@Tom Hawtin:可能更准确地说C ++,C#和Java都来自C语言系列:)

答案 7 :(得分:1)

来自perl背景我认为C#密封了每个可能想要通过非虚方法扩展和修改基类行为的开发人员的厄运,而不会强迫新类的所有用户都知道幕后细节。

考虑List类的Add方法。如果开发人员想要在特定列表“添加”时更新几个潜在数据库中的一个,该怎么办?如果默认情况下“添加”是虚拟的,开发人员可以开发一个“BackedList”类,该类覆盖“添加”方法,而不会强制所有客户端代码知道它是'BackedList'而不是常规'List'。出于所有实际目的,'BackedList'可以被视为来自客户端代码的另一个'List'。

从大型主类的角度来看,这是有道理的,它可以提供对一个或多个列表组件的访问,这些组件本身由数据库中的一个或多个模式支持。鉴于C#方法默认情况下不是虚拟的,主类提供的列表不能是简单的IEnumerable或ICollection,甚至不能是List实例,而必须作为'BackedList'广告给客户端,以确保新版本调用“添加”操作以更新正确的模式。

答案 8 :(得分:0)

这当然不是性能问题。 Sun的Java解释器使用相同的代码来调度(invokevirtual字节码),而HotSpot生成完全相同的代码,无论是否final。我相信所有C#对象(但不是结构)都有虚方法,所以你总是需要vtbl /运行时类标识。 C#是“类Java语言”的方言。建议它来自C ++并不完全诚实。

有一种想法,你应该“设计继承或禁止它”。这听起来像个好主意,直​​到你有一个严厉的商业案例来进行快速修复。也许继承自你无法控制的代码。

相关问题