我应该将所有方法标记为虚拟吗?


74

在Java中,您可以将method标记为final,以使其无法覆盖。

在C#中,您必须将方法标记为虚拟方法,以使其能够被覆盖。

这是否意味着在C#中,您应该将所有方法标记为虚拟方法(除了一些您不想被覆盖的方法之外),因为您很可能不知道可以通过什么方式继承您的类?


1
我想认为在C#中,如果您做的事情有些挑剔,那您就做错了。
Louis Kottmann

4
@KonradRudolph,请引文/来源!
bestsss 2013年

1
@bestsss不,我绝对不会。最终字段完全无关。
康拉德·鲁道夫2013年

1
@bestsss忘记final字段,我知道他在谈论字段。但是我认为他谈到了有效Java中的最终方法。但是,我可能会忘记这一点。我可以确定,他可能会在其他演讲中提到它。
康拉德·鲁道夫2013年

Answers:


140

在C#中,您必须将方法标记为虚方法,以使其能够被覆盖。这是否意味着在C#中,您应该将所有方法标记为虚拟方法(除了一些您不想被覆盖的方法之外),因为您很可能不知道可以通过什么方式继承您的类?

否。如果语言设计人员认为virtual应该是默认值,那么它将是默认值

可覆盖性是一种功能,与所有功能一样,它具有成本。可重写方法的成本是巨大的:设计,实现和测试成本很高,尤其是对类有“敏感性”时;虚拟方法是将未经测试的第三方代码引入系统的方式,并且具有安全性。

如果您不知道如何继承您的类,请不要发布您的类,因为您尚未完成对它的设计。您的扩展性模型绝对是您应该提前知道的;它会深刻影响您的设计和测试策略。

我主张所有类都是密封的,并且所有方法都是非虚拟的,直到您有一个以客户为中心的真实世界的理由来解封或将方法虚拟化。

基本上,您的问题是“我不知道我的客户打算如何消费我的课程;因此我应该使它可以任意扩展吗?” 没有; 你应该变得博学多才!您不会问“我不知道我的客户将如何使用我的类,所以我应该将我的所有属性都读写吗?并且应该让我的所有方法都将委托类型的属性读写给我,以便我的用户可以用自己的实现替换任何方法吗?” 不,在您有证据证明用户确实需要该功能之前,请勿执行任何此类操作!花费宝贵的时间来设计,测试和实现用户真正想要和需要的功能,并且从知识的角度去做。


17
+100表示​​“所有类都是密封的,所有方法都是非虚拟的,直到您有一个以客户为中心的真实世界的理由才可以解封或将方法虚拟化。”
tallseth

15
我同意这个答案,但是您主张类sealed默认为类,而不是默认类,我觉得很有趣。
马修(Matthew)

6
@PopCatalin您的假设是将更多内容虚拟化可以保护您的客户。实际上,它使客户更多地暴露于您的错误中。
deworde

27
-1我也不同意。您不必用类的所有已知用例来限制软件的可扩展性。密封+非虚拟类型禁止测试以及类型以及使用该类型的任何软件的可扩展性。您可以将System.Web视为当今使用了十多年的最受摩擦困扰且不可测试的HTTP协议栈之一,距今已有十多年的历史了,我们仍然不乏具体的ASP.NET类型及其不可替代的暗示。在尝试了新的Web抽象之后,MS本身无法在其不同的HTTP堆栈之间共享功能
神话

12
我认为在以下方面做出区分可能重要:在此之间的“适当”答案。“为他人服务”和“ ppl w访问以后使用的代码来更改源”。.NET fwk中的非虚拟和密封(作为示例)用于从未有过的地方,并且该fwk的消费者仍然怀有10多年的关于“必要”可扩展性点的“假设”。您无法预测ppl将如何/需要扩展您的类,因此,尽管“默认默认virt”反之亦然,但默认情况下密封是同样糟糕的选择IMO。
sbohlen 2013年

28

在我看来,当前接受的答案是不必要的教条主义。

事实是,当您未将方法标记为时virtual,其他人将无法覆盖其行为,并且当您将类标记为sealed其他人无法从该类继承时。这会引起严重的疼痛。我不知道我诅咒一个API来标记类sealed或不标记方法的次数virtual仅仅是因为他们没有预料到我的用例。

从理论上讲,这可能是正确的方法,只允许重写和继承要被重写和继承的类,但实际上,不可能预见所有可能的情况,因此确实没有充分的理由将其封闭。

  1. 如果您没有很好的理由,请不要将类标记为 sealed
  2. 如果您的库打算供他人使用,则至少尝试将包含该行为的类的主要方法标记为virtual

进行调用的一种方法是查看方法或属性的名称。一对GetLength()在列表的方法不正是顾名思义,它不允许太多的解释。更改其实现可能不是很透明,因此将其标记为virtual不必要的。将Add方法标记为虚拟方法更为有用,因为有人可以创建一个特殊的列表,该列表仅通过Add方法等接受某些对象。另一个示例是自定义控件。您可能希望使用主要的绘制方法,virtual以便其他人可以使用大部分行为并仅更改外观,但您可能不会覆盖X和Y属性。

最后,您通常不必立即做出决定。在内部项目中,无论如何您都可以轻松更改代码,我不会担心这些事情。如果需要重写某个方法,则总是可以在发生这种情况时将其设为虚拟。相反,如果项目是别人使用的API或库,并且更新缓慢,那么考虑哪些类和方法可能是有用的,那肯定是值得的。在这种情况下,我认为最好是开放而不是严格关闭。


22

没有!因为您不知道如何继承您的类,所以应将方法标记为virtual好像您知道要覆盖它一样。


4

否。只有要让派生类指定的方法才应该是虚拟的。

虚拟与最终无关。

为了防止在C#中覆盖虚拟方法,请使用 sealed

public class MyClass
{
    public sealed override void MyFinalMethod() {...}
}

5
仅供参考,以Javafinal的方式表示not virtual
约翰·桑德斯

3

我们可以想出/重新建立任何一个阵营的理由,但这完全没有用。

在Java中,有数百万种意想不到的非最终公共方法,但是我们听到的恐怖故事很少。

在C#中,有数百万种密封的公共方法,而且我们很少听到恐怖故事。

因此,这没什么大不了的-重写公共方法的需求很少,因此无论哪种方式都无济于事。


这让我想起了另一个论点-默认情况下局部变量是否应为final。这是一个很好的主意,但我们不能夸大它的价值。有数十亿个局部变量可能是但不是最终变量,但事实证明这是一个实际问题。


2

是的你应该。我希望回答的答案与大多数其他答案不同。这是C#中的缺陷。缺陷。设计错误。与Java相比,您可以看到所有方法都是“虚拟”的,除非另有说明(“最终”)。当然,如果存在带有“区域”方法的“矩形”类,并且您希望有自己的类以边距表示“矩形”。您希望利用现有类的所有属性和方法,并且只想添加一个“ margin”属性,为常规矩形区域添加一些值,并且如果Rectangle中的area方法未标记为virtual,你注定了。图片,请使用矩形数组并返回所有矩形的面积之和的方法。有些可以是规则的矩形,有些可以带有边距。现在,读回标记为“正确”的答案,描述“安全问题”或“测试”。与无能力超越相比,这些毫无意义。

别人回答“不”,我并不感到惊讶。我很惊讶C#的作者在将语言基于Java时看不到它。


0

将方法虚拟化通常会减慢需要调用它的所有代码。这种减慢将是微不足道的,但在某些情况下可能会很大(除其他外,因为可以内联非虚拟方法调用,这又可以使优化程序消除不必要的操作)。并非总是能够预测虚拟调用可能影响执行速度的程度,通常应该使做一些会使代码变慢的事情无效,除非这样做有明显的好处。

在许多情况下,使方法成为非虚拟方法的性能好处可能足以证明默认情况下方法为非虚拟方法,但是当将类设计为继承时,大多数方法应该是虚拟的且未密封。非虚拟或密封方法的主要用途应该是其他(可能受保护的)虚拟方法的包装器(要更改基础行为的代码应覆盖适当的虚拟方法,而不是包装器)。

将类标记为sealed或将继承限制为程序集中其他类的原因常常与性能无关。其中,如果一个类是可外部继承的,则具有protected作用域的所有成员都会有效地添加到其公共API中,并且其在基类中的行为的任何更改都可能破坏依赖该行为的任何派生类。另一方面,如果一个类是可继承的,则使用其方法virtual并不会真正增加其暴露程度。如果有的话,它可以通过允许派生类完全“掩埋”与派生类不再相关的基类实现方面来减少派生类对基类内部的依赖(例如,如果List<T>如果是虚拟的,则覆盖它们的派生类可以使用数组数组来保存事物(避免大对象堆问题),而不必尝试使使用的私有数组List<T>与数组保持一致。数组。


0

不,您不应将所有方法都标记为虚拟方法。您应该考虑如何继承您的类。如果不应继承该类,则将其标记为密封,并且显然成员不应为虚拟成员。如果您的类很可能会被继承,则您确实应该最大限度地提高覆盖行为的能力。因此,除非有理由不要在此类中的任何地方大范围使用虚拟。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.