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

Posted

技术标签:

【中文标题】我应该将所有方法标记为虚拟吗?【英文标题】:Should I mark all methods virtual? 【发布时间】:2013-01-05 06:09:25 【问题描述】:

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

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

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

【问题讨论】:

我喜欢这样认为,在 C# 中,如果你做的事情让人感觉很挑剔,那你就做错了。 @KonradRudolph,请引用/来源! @bestsss 不,我绝对不。 final 字段完全不相关。 @bestsss 忘记final 字段,我知道他在谈论它们。但我认为他谈到了Effective Java中的最终方法。但是,我可能记错了。我敢肯定,尽管他当时在其他地方提到过——也许是在他的一次演讲中。 让我们continue this discussion in chat 【参考方案1】:

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

没有。如果语言设计者认为 virtual 应该是默认设置,那么 它应该是默认设置

可覆盖性是一个特性,与所有特性一样,它也有成本。可覆盖方法的成本相当高:设计、实现和测试成本很高,特别是如果对类有任何“敏感性”;虚拟方法是将未经测试的第三方代码引入系统的方法,会产生安全影响。

如果你不知道你打算如何继承你的类,那么不要发布你的类,因为你还没有完成它的设计。你的可扩展性模型绝对是您应该提前知道的;它应该深刻地影响您的设计和测试策略。

我主张所有类密封,所有方法非虚拟,直到你有一个真正的客户为中心的理由来解封或使方法虚拟.

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

【讨论】:

+100 表示“所有类都被密封,所有方法都是非虚拟的,直到你有一个以客户为中心的真实理由来解封或使方法成为虚拟。” 我同意这个答案,但我觉得很有趣的是,您提倡默认情况下类应该是 sealed,但事实并非如此。 @PopCatalin 你的假设是让更多的东西虚拟化可以保护你的客户。实际上,它会让客户接触到您的错误更多 -1 我也不同意。您不必使用类的所有已知用例来限制软件的可扩展性。密封 + 非虚拟类型会抑制测试和您的类型以及使用您的类型的任何软件的可扩展性。您可以将 System.Web 视为当今现代使用中最受摩擦且无法测试的 HTTP 堆栈之一,10 多年过去了,我们仍然没有没有具体的 ASP.NET 类型及其不可替代的 impl。在多次尝试新的 Web 抽象之后,MS 本身无法在其不同的 HTTP 堆栈之间共享功能 我认为在 re: 此处的“适当”答案中做出区分可能非常很重要。 “fwks for others”和“ppl w access 以后更改源代码使用的代码”。 .NET fwk 中的非虚拟和密封(例如)被用在了它本不应该出现的地方,并且该 fwk 的消费者仍然为那些 10 多年前关于“必要”可扩展性点的“假设”所困扰。您无法预测 ppl 将如何希望/需要扩展您的类,因此虽然“默认情况下”可能太过分了,但默认情况下密封是一个同样糟糕的选择 IMO。【参考方案2】:

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

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

理论上,只允许重写方法和继承本应被重写和继承的类可能是正确的方法,但实际上不可能预见所有可能的情况,而且确实没有充分的理由如此封闭.

    如果您没有充分的理由,请不要将课程标记为 sealed。 如果您的库打算供其他人使用,那么至少尝试将包含行为的类的主要方法标记为virtual

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

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

【讨论】:

【参考方案3】:

不!因为您不知道您的类将如何被继承,所以您应该将一个方法标记为virtual,如果您知道您希望它被覆盖。 p>

【讨论】:

将方法标记为虚拟只是因为我需要模拟单元测试是一种好习惯吗? (即我的业务逻辑不需要它是虚拟的)。 @joe 我会拒绝。如果可能,请使用接口,或使用与非虚拟方法一起使用的模拟技术之一。例如,TypeMock。创建一个方法virtual 承诺支持该方法的所有可能覆盖。这需要适当地设计到类中。【参考方案4】:

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

virtual 与 final 无关。

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

public class MyClass

    public sealed override void MyFinalMethod() ...

【讨论】:

仅供参考,在 Java 中,final 在方法上的意思是 not virtual【参考方案5】:

我们可以想出支持/再次支持任一阵营的理由,但这完全没用。

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

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

所以这没什么大不了的 - 覆盖公共方法的需要很少,所以无论哪种方式都没有实际意义。


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

【讨论】:

【参考方案6】:

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

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

【讨论】:

历史小细节:C# 基于 C/C++。当然和Java类似,因为Java也是基于C/C++的。我两个都用,发现没有必要“因为我用另一个而讨厌一个”。 我不明白为什么不同于 java 会自动使其成为“缺陷”。如果您不在 c# 中将其标记为虚拟,则意味着您不希望子类具有修改行为的能力。如果您希望用户进行更改,请明确将其标记为虚拟。只是不同,不能说一个是否比另一个好 100%。 整个想法,继承的基本概念,扩展类的核心概念,是原始类的作者,没有考虑到其他程序员的使用,因此,扩展/inhertiance 允许任何程序员采用现有的类并将其扩展为他们自己的需要!它背后的想法比 Java 和 C# 之间的比较要大得多,无论谁错过了这一点,都没有完全理解面向对象的编程。因此,使它成为“缺陷”的并不是它与 java 的不同,而是它缺少其背后的想法。 en.wikipedia.org/wiki/SOLID【参考方案7】:

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

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

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

【讨论】:

【参考方案8】:

不,您不应该将所有方法都标记为虚拟方法。您应该考虑如何继承您的类。如果该类不应该被继承,那么将其标记为密封的,显然成员不应该是虚拟的。如果你的类很可能被继承,你真的应该最大化覆盖行为的能力。因此,除非您有理由不这样做,否则请在此类课程中到处使用 virtual。

【讨论】:

以上是关于我应该将所有方法标记为虚拟吗?的主要内容,如果未能解决你的问题,请参考以下文章

Azure PowerShell - 虚拟机标记报告

你会拥有太多的“受保护的虚拟”方法吗?

标记 - 清除算法

标记 - 清除算法

每个类都应该有一个虚拟析构函数吗?

java虚拟机--垃圾收集