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

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

我想起了从Anders Hejlsberg那里读到一段关于现有架构带来的几个优点。 但是,副作用呢? 默认情况下有非虚拟方法真的是一个很好的权衡?

类应该被devise为inheritance,以便能够利用它。 缺省的virtual方法意味着类中的每个函数都可以被replace掉,这并不是一件好事。 许多人甚至认为,class级应该是默认sealed的。

virtual方法也可以有一个轻微的性能影响。 但这不可能是主要原因。

我很惊讶,在这里似乎有这样一个共识,非虚拟默认是正确的做事方式。 另一方面,我会下来 – 我认为篱笆是实用的一面。

大部分的理由都像旧的“如果我们给你的权力,你可能会伤害自己”的说法。 来自程序员?

在我看来,像编码器谁不知道足够的(或有足够的时间)来devise他们的图书馆的inheritance和/或可扩展性是编码器谁正在生产我可能需要修复或调整的库 – 正是图书馆,其中覆盖的能力将最有用。

我不得不编写丑陋,绝望的解决方法代码(或者放弃使用和推出我自己的替代解决scheme)的次数,因为我无法覆盖很远,远远超过了我曾经被咬过的次数(例如在Java中)通过覆盖devise者可能没有考虑到的地方。

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

更新:已经指出[非常正确]我没有真正回答这个问题。 所以 – 并道歉迟到….

我有点想写一些精巧的东西,比如“C#实现的方法默认是非虚拟的,因为做了一个比程序员更重视程序的不好的决定”。 (我认为基于这个问题的其他一些答案 – 比如性能(不成熟的优化,任何人?),或者保证类的行为,都可能有些合理。)

但是,我意识到我只是陈述我的意见,而不是Stack Overflow所期望的确切答案。 当然,我认为,在最高层次上,最终的(但无益的)答案是:

它们默认情况下是非虚拟的,因为语言devise者做出了决定,而这正是他们select的。

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

因此,在这里关于重写API的风险以及明确deviseinheritance的必要性的答案和评论似乎是正确的,但都缺less一个重要的时间方面:Anders的主要关注是维护一个类或API的隐式合同跨版本 。 而且我认为他实际上更关心允许.Net / C#平台在代码中更改,而不是关心在平台之上更改用户代码。 (而他的“实用”观点与我正好相反,因为他从另一面看)

(但是,他们不能仅仅通过默认select虚拟,然后通过代码库进行“最终”的select吗?也许这不是完全一样,而且Anders显然比我更聪明,所以我会让它谎言。

因为很容易忘记一个方法可能被忽略,而不是为此而devise的。 C#让你思考,然后让它变得虚拟。 我认为这是一个伟大的devise决定。 有些人(比如Jon Skeet)甚至说,class级应该被默认封闭。

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

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

2-默认情况下每个方法都是虚拟的,这是性能问题,因为每个方法调用都必须使用对象的虚拟表。 此外,这严重限制了即时编译器内联方法和执行其他types优化的能力。

3-最重要的是,如果方法默认不是虚拟的,你可以保证你的类的行为。 当它们在默认情况下是虚拟的,比如在Java中,你甚至不能保证一个简单的getter方法能够按照预期执行,因为它可以在派生类中被重写(当然你可以并且应该使方法和/或类最终)。

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

C#受C ++(以及更多)的影响。 C ++默认情况下不启用dynamic分派(虚拟function)。 对此的一个(好的)论点是这样一个问题:“你多久执行一次class级成员的class级?”。 避免启用dynamic分派的另一个原因是内存占用。 没有指向虚拟表的虚拟指针(vpointer)的类当然小于启用了后期绑定的相应类。

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

另一个关于“ 虚拟呼叫速度 ”的类似问题

简单的原因是devise和维护成本以及性能成本。 与非虚拟方法相比,虚拟方法具有额外的成本,因为类的devise者必须规划当方法被另一个类覆盖时发生的情况。 如果您希望某种方法更新内部状态或具有特定行为,这会产生很大的影响。 您现在必须计划派生类改变行为时发生的情况。 在这种情况下编写可靠的代码要困难得多。

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

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

如果类定义了虚方法,C#对象只有虚方法。 确实,所有的对象都有types信息,包括一个vtbl等价物,但是如果没有定义虚拟方法,那么只有基础的对象方法会出现。

@Tom Hawtin:可能更准确的说C ++,C#和Java都来自C语言家族:)

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

有一个想法,你应该“deviseinheritance,否则禁止它”。 这听起来像是一个伟大的想法,直到你有一个严重的商业案例来快速修复。 也许inheritance自你不能控制的代码。

从perl背景来看,我认为C#封闭了每个开发人员的厄运,他们可能希望通过非虚拟方法扩展和修改基类的行为,而不强迫新类的所有用户意识到潜在的幕后细节。

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

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