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

时间:2013-01-22 03:54:56

标签: c# java methods virtual-method

在Java中,您可以将方法标记为final,使其不可能覆盖。

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

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

8 个答案:

答案 0 :(得分:136)

  

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

没有。如果语言设计者认为虚拟应该是默认值,那么它将是默认值

Overridablility是功能,与所有功能一样,它具有成本。可重复方法的成本相当可观:设计,实施和测试成本很高,特别是如果对课程有任何“敏感性”;虚方法是将未经测试的第三方代码引入系统并具有安全影响的方法。

如果您不知道如何继承您的课程,那么不会发布您的课程,因为您还没有完成设计。您的可扩展性模特肯定是你应该提前知道的;它应该深刻影响你的设计和测试策略。

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

基本上你的问题是“我不知道我的客户打算如何消费我的课程;我是否应该让它任意扩展?”没有;你应该变得知识渊博!你不会问“我不知道我的客户将如何使用我的类,所以我应该把我的所有属性都读写吗?我应该让我的所有方法读写代理类型的属性,以便我的用户可以用自己的实现替换任何方法吗?“不,在您有证据用户实际需要该功能之前,请不要执行任何操作!花费宝贵的时间设计,测试和实现用户真正想要和需要的功能,并从知识的位置开始。

答案 1 :(得分:24)

在我看来,目前接受的answer是不必要的教条。

事实是,当您没有将方法标记为virtual时,其他人无法覆盖其行为,当您将某个类标记为sealed时,其他人无法继承该类。这可能会导致严重的疼痛。我不知道有多少次我诅咒API标记类sealed或者没有标记方法virtual只是因为他们没有预料到我的用例。

理论上,它可能是正确的方法,只允许覆盖方法和继承类被覆盖和继承的类,但在实践中,不可能预见每一种可能的情况,并且确实没有充分的理由如此关闭。

  1. 如果你没有充分的理由那么就不要把课程标记为 sealed
  2. 如果您的库要供其他人使用,那么至少要尝试将包含行为的类的主要方法标记为virtual
  3. 进行调用的一种方法是查看方法或属性的名称。 List上的 GetLength()方法完全符合名称的含义,并且它不允许解释的大部分内容。更改其实现可能不是非常透明,因此将其标记为virtual可能是不必要的。将Add方法标记为虚拟更有用,因为有人可以创建一个特殊的List,它只通过Add方法等接受一些对象。另一个例子是自定义控件。您可能希望制作主绘图方法virtual,以便其他人可以使用大部分行为并只更改外观,但您可能无法覆盖X和Y属性。

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

答案 2 :(得分:22)

没有!由于您不知道如何继承您的类,因此如果您知道要覆盖您的类,则将方法标记为virtual

答案 3 :(得分:5)

没有。只有您希望派生类指定的方法才应该是虚拟的。

虚拟与最终无关。

要防止覆盖c#中的虚拟方法,请使用sealed

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

答案 4 :(得分:4)

我们可以想出/再次营地的原因,但这完全没用。

在Java中有数以百万计的无意识的非最终公共方法,但我们听到的恐怖故事很少。

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

所以这不是什么大问题 - 覆盖公共方法的需求很少,所以无论如何都没有用。


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

答案 5 :(得分:2)

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

其他人回答“不”,我并不感到惊讶。 我很惊讶C#的作者在将他们的语言基于Java时看不到这一点。

答案 6 :(得分:1)

将方法设为虚拟通常会减慢需要调用它的任何代码。这种减速将是微不足道的,但在某些情况下可能非常大(除此之外,因为非虚拟方法调用可能是内联的,这可能反过来允许优化器消除不必要的操作)。并不总是可以预测虚拟调用可能影响执行速度的程度,并且通常应该做一些会使代码变慢的事情,除非这样做有明显的好处。

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

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

答案 7 :(得分:0)

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