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

Geo*_*kov 71 c# java methods virtual-functions

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

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

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

Eri*_*ert 137

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

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

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

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

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

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

  • -1我也不同意.您不必使用类的所有已知用例来限制软件的可扩展性.密封+非虚拟类型会抑制您的类型和使用您的类型的任何软件的测试和可扩展性.您可以将System.Web视为当今现代使用中摩擦最大且不可测试的HTTP堆栈之一,10年以上,我们仍然没有具体的ASP.NET类型及其不可替代的impls.在多次尝试新的Web抽象之后,MS本身无法在不同的HTTP堆栈之间共享功能 (27认同)
  • +100表示​​"所有类别都是密封的,所有方法都是非虚拟的,直到您有一个真实的,以客户为中心的理由来开启或使方法成为虚拟的." (17认同)
  • 我同意这个答案,但我觉得很有趣的是你提倡默认情况下类应该"密封",而不是. (14认同)
  • 我认为重新区分可能非常重要:这里的"适当"答案."fwks for others"和"ppl w使用的代码可以在以后更改源代码".非虚拟和密封在.NET fwk(作为一个例子)被用于它本来就不应该存在的地方,并且那些fwk的消费者仍然背负着关于"必要的"可扩展性点的那些10岁以上的"假设".你不能预测ppl将如何想要/需要扩展你的类,所以虽然'virt-by-default'可能太过分了,但默认密封是一个同样糟糕的选择IMO. (12认同)
  • @PopCatalin您的假设是虚拟更多东西可以保护您的客户.实际上,它会让客户面对你的错误*更多*. (6认同)
  • 不敢相信我在这里不同意Eric Lippert,但我和@mythz和sbohlen在这个问题上有所不同.如前所述,ASP.NET是Fx的完美示例,它是一个正确测试/扩展的皇家PITA,因为设计密封/内部/非基于接口的设计选择不佳.如果您正在为内部消费构建库并且您控制所有消费者,那么是的,稍后更改并发布新版本并不是什么大问题.如果你打算像ASP.NET那样保持向后兼容,那么你必须从跳转中获得测试/可扩展性. (6认同)
  • 好吧,所有喜欢把所有东西都变成虚拟和未密封的人:你是否也使所有属性都可写?你不知道这对用户来说是否方便,所以你应该这样做,对吧?为什么开封的论点也不适用于你不习惯提供的其他千种可扩展性中的任何一种? (6认同)
  • @Firo:如果一个方法采用具体的密封类型作为参数,那是因为*他们不希望你改变传入对象语义的细节*.设计,实现和测试带有疯狂,意外行为的参数的代码是*昂贵的*.以混凝土密封型为参数可以降低每个人的成本.而且,弦乐呢?这些是混凝土密封型.您是否认为每个接受字符串的方法都必须处理字符串,比如说,其长度属性与其中的字符数不同? (5认同)
  • -1因为我不是真的同意.我个人坚持开/闭原则.我不知道我的课程将如何扩展,所以我做了一个有根据的猜测.稍后,我可以根据客户反馈改进我的选择.这样客户就不会受阻,我们都可以继续前进. (4认同)
  • @KonradRudolph我说类**应该是接口**(而不是相反)所以我们不会被强制绑定到密封的+具体实现.正如您可能知道的那样,没有**与ASP.NET类型组合的可能性,您会遇到暴露在API表面区域的具体冲突.因此,虽然构成优于继承应该是首选,但这是不可能的. (4认同)
  • 这个理论真的很合理,如果你想成为一个血腥的独裁者,想要将你的错误的所有代价转嫁给你的客户:我没有想到这一点,所以你运气不好,你不是我期待的典型客户.这是一个很好的理论,直到你在另一个接收端,当你基本上需要一些东西而你不能通过简单地添加一些行为来做到这一点,而是你意识到你需要做一些最愚蠢的解决方法.我同意安全问题,这些都是有效的,但这样做是为了节省你的屁股并削减"你的"支持费用只是坏事. (3认同)
  • @mythz您可以按组合方式共享/重用代码.如果基类没有为此设计*,则无法有意义地替换实现部分.将方法设为虚拟是不够的,您必须在设计具有可扩展性的类时.事实上,你说这些类"不应该有接口"但应该是虚拟的是bunk:如果接口不合适,那么它们都不是虚方法. (3认同)
  • 有趣的是,迫使微软不将许多方法虚拟化(或没有其他可扩展点)的纯粹技术考虑似乎在开源项目中很少出现。 (3认同)
  • -1 派生类的实现是实现者的责任,而不是原始开发者的责任。相比之下,Java 可以轻松扩展类,并且与 @Override 注释配合得很好。在.Net中,总是请求闭源开发人员将非虚拟方法更改为虚拟方法是一件很痛苦的事情。这种事永远不会发生。 (3认同)
  • 在可扩展的公共API中进行类充满危险的原因是,在您这样做的那一刻,您必须抛弃您在设计中对不变量做出的所有假设.你编写的每一个方法都必须完全验证你的类内部状态,因为你无法知道从你那里派生一个新类的人没有搞砸(不是故意的;但只是因为他们无法知道你所做的一切)你设计了它).这会变成许多额外的验证代码,因为您以前的所有内部状态都必须被视为未经过验证的用户输入. (2认同)
  • 完全同意@sbohlen。几年前,当我使用 C# 工作时,但我仍然记得对密封类的数量以及无法扩展/覆盖但引入包装而感到不满。 (2认同)
  • @KonradRudolph System.Web的失败就是这种精确的文化和密集的具体类型的扩散,这些类型已经接近扩展.我们应该**永远不会被强制绑定密封的具体类型,因为所有代码仅对框架提供的单个实现(即ASP.NET)起作用.AFAIK没有其他流行的Web平台受此影响,Java使用接口和动态语言框架(本质上)没有这个问题. (2认同)
  • @mythz:问题不在于"ASP的扩展性模型是否设计良好?",尽管这是一个有趣的问题.问题是"将所有方法都虚拟化是一个好主意吗?" (2认同)
  • @EricLippert 我强调了这一推理的后果并使用 ASP.NET 作为示例。我认为默认情况下使用“非虚拟”(和密封)的后果是不合理的(鉴于大多数替代语言不强制执行它)。实践中真正的问题是无法扩展/覆盖内置/第三方行为。就像 Java 的检查异常一样,我认为非虚拟在理论上很好,但在实践中却失败了。 (2认同)
  • Eric Lippert在他的博客文章中更详细地讨论了他的观点,[为什么这么多的框架类被密封?](http://blogs.msdn.com/b/ericlippert/archive/2004/01/22/61803. ASPX) (2认同)
  • @KonradRudolph你可以通过使用合成来扩展,但有时候它并没有真正发挥作用,你最终会得到很多代理方法.我不希望所有东西都被打开,比如"所有属性公开!",但我认为对于哪些方法是虚拟方法以及哪些方法不是比密封所有方法更有意义,直到客户要求"虚拟化" (2认同)
  • @formix:您是否建议所有C#基类都具有委托类型的公共字段,以便任何用户可以随意替换该方法?如果没有,为什么不呢?该机制比虚拟覆盖更灵活,虚拟覆盖只能通过子类替换方法.为什么想要改变类行为的人必须求助于子类化? (2认同)
  • 而其他语言在某种程度上具有完全可重写的能力。这就是为什么,例如,Ruby 有如此多的 gem 和一个庞大的社区,而在 dotnet 中,你几乎必须自己编写所有内容,并在 github 上使用许多废弃的库。 (2认同)

Pat*_*lug 26

在我看来,目前接受的答案是不必要的教条.

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

从理论上讲,它可能是一种正确的方法,只允许重写方法和继承那些意图被覆盖和继承的类,但在实践中,不可能预见到每一种可能的情况,并且确实没有充分理由这么封闭.

  1. 如果你没有很好的理由那么就不要把课程标记为 sealed.
  2. 如果你的库要被其他人使用,那么至少要尝试将包含行为的类的主要方法标记为virtual.

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

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


Joh*_*ers 22

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


nun*_*cal 5

不可以.只有您希望派生类指定的方法才应该是虚拟的.

虚拟与最终无关.

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

public class MyClass
{
    public sealed override void MyFinalMethod() {...}
}
Run Code Online (Sandbox Code Playgroud)

  • 仅供参考,在Java中,方法的"final"意味着"非虚拟". (5认同)