为什么要上课?


96

我想听听.Net框架中大量密封类背后的动机是什么。补习班有什么好处?我无法理解不允许继承如何有用,而且很可能不是唯一与这些阶级作斗争的人。

那么,为什么以这种方式设计框架,而将所有东西都密封起来,这将是不懈的改变吗?一定还有别的原因,只是邪恶?



Answers:


41
  • 有时,类太珍贵,不能设计成无法继承。
  • 查找类型时,运行时/反射可以对密封类进行继承假设。一个很好的例子是-建议密封属性以提高查找运行时的速度。如果将MyAttribute密封,则type.GetCustomAttributes(typeof(MyAttribute))的执行速度将明显加快。

有关该主题的MSDN文章是“ 通过密封类限制可扩展性”


3
很高兴看到他们现在清楚地说“谨慎使用”……希望他们能实践讲道。
mmiika

13
对我来说,这似乎是个坏建议:(
Jon Skeet

4
@CVertex:对不起,我不是要批评你-只是这篇文章。
乔恩·斯基特

16
@generalt:我相信为继承而设计或禁止继承。为继承而设计需要大量的工作,并且将来经常会限制实现。继承还将调用者的确切不确定性引入调用者。它也不能与不变性很好地融合在一起(我很喜欢)。我只发现类继承在相对较少的地方有用(而我喜欢接口)。
乔恩·斯基特

1
@CVertex如果您使用过.NET,则可能会遇到问题并且没有注意到,几乎所有.NET核心类都是密封的。
CoryG '16

103

类应该设计为继承或禁止继承。设计继承需要付出代价:

  • 它可以确定实现(您必须声明哪些方法将调用其他方法,以防用户重写一个方法而不是另一个方法)
  • 它揭示了您的实现方式,而不仅仅是效果
  • 这意味着您在设计时必须考虑更多可能性
  • 像Equals这样的东西很难在继承树中设计
  • 需要更多文档
  • 子类化的不可变类型可能变得可变(ick)

关于有效Java的条款17对此进行了更详细的介绍-无论它是在Java上下文中编写的,该建议也适用于.NET。

我个人希望在.NET中默认密封类。


26
嗯..如果您延伸班级,是不是您打破班级的问题呢?
mmiika

25
如果您无法控制基类中的实现更改而使您烦恼怎么办?那是谁的错 继承基本上会引入脆弱性。国际海事组织,赞成继承而不是继承可以提高鲁棒性。
乔恩·斯基特

6
是的,界面很好-是的,无论如何您都可以喜欢合成。但是,如果我在未仔细考虑的情况下公开未密封的基类,则应该期望所做的更改很可能会破坏派生类。对我来说,这感觉很糟糕。IMO最好密封等级,避免破损。
乔恩·斯基特

8
@琼:作曲是一种“有一个”关系,而不是“是一个”。因此,如果您想编写一个在某些方面可以像列表一样工作的类,而在另一些方面则不然,则可能要创建一个具有List <T>成员变量的类,而不是从List <T>派生。然后,您将使用该列表来实现各种方法。
乔恩·斯基特

3
@ThunderGr:但这就是我的观点:当您不必担心子类提供了哪些其他实现时,您可以使用自己的实现更自由。例如,假设一个方法是通过调用另一个方法来实现的,并且两者都是虚拟的。即使感觉像实现细节,也需要记录下来。基本上,为继承而设计会添加手铐-在某些情况下适用,而在其他情况下则不适用。我宁愿有一个密封的类来实现一个接口,而不是一个带有一堆虚拟方法的未密封的类。
乔恩·斯基特

8

自从大约9年前提出此问题以来,微软官方的密封指南似乎已经发展起来,并且从选择加入的哲学(默认为密封)变为选择退出(默认为不密封):

X不要在没有充分理由的情况下密封课程。

因为您无法考虑可扩展性场景而封闭类并不是一个很好的理由。框架用户喜欢出于各种非显而易见的原因而从类继承,例如添加便利成员。有关用户要从类型继承的非显而易见原因的示例,请参见未密封类。

密封课程的充分理由如下:

  • 该类是静态类。请参见静态类设计。
  • 该类将安全敏感的机密存储在继承的受保护成员中。
  • 该类继承了许多虚拟成员,单独密封它们的成本将超过使该类不密封的好处。
  • 该类是需要非常快速的运行时查找的属性。密封属性的性能水平比未密封属性高。请参阅属性。

X请勿在密封类型上声明受保护的成员或虚拟成员。

根据定义,密封类型不能继承。这意味着不能调用密封类型上的受保护成员,并且不能覆盖密封类型上的虚拟方法。

✓考虑您要覆盖的密封件。引入虚拟成员(在虚拟成员中讨论)可能导致的问题也适用于替代,尽管程度较小。从继承层次结构中的这一点开始,密封替代将使您免受这些问题的困扰。

的确,如果您搜索ASP.Net Core代码库,则只会发现约30种情况sealed class,其中大多数是属性和测试类。

我确实认为,不变性守恒是支持密封的一个很好的论据。


4

我在msdn文档中发现了这句话:“密封类主要用于防止派生。因为它们永远不能用作基类,所以某些运行时优化可以使调用密封类成员的速度稍快一些。”

我不知道性能是否是密封类的唯一优势,我个人也想知道其他原因...


4
看到他们在谈论什么样的性能优势会很有趣...
mmiika

3

性能是一个重要因素,例如,java中的字符串类是final(<-密封),其原因仅是性能。我认为另一个重要的点是避免在此处详细描述的脆弱的基类问题:http : //blogs.msdn.com/ericlippert/archive/2004/01/07/virtual-methods-and-brittle-base-classes。 aspx

如果提供框架,那么对于遗留项目的可维护性以及升级框架以避免脆弱的基类问题非常重要。


Java中String最终的原因不是性能,而是安全。
CesarB

@CesarB:是的,但是String不是普通的Java类。它是Java中唯一支持运算符重载的类(有关更多信息,请参见此处的部分:“即使C和Java也具有(硬编码)运算符重载”),这是普通类无法实现的。因此,String该类甚至可能不是子类,即使它不是最终的。
wchargin 2012年


0

密封可以使您获得一些次要的性能提升。在JIT和懒惰的悲观世界中,这比在C ++中的情况要少,但是由于.NET不如Java编译器那样悲观,主要是因为设计原理不同,所以它仍然有用。它告诉编译器可以直接调用任何虚拟方法,而不必通过vtable间接调用它们。

当您需要“封闭世界”进行平等比较之类的事情时,这一点也很重要。通常,一旦定义了虚拟方法,我就会非常费力地定义真正实现该想法的相等比较概念。另一方面,我也许可以使用虚拟方法为该类的特定子类定义它。密封该类可确保平等确实成立。



0

要确定是密封类,方法还是属性,通常应考虑以下两点:

•通过自定义班级的能力,派生班级可能获得的潜在利益。

•派生类可能以无法正常工作或无法正常工作的方式修改您的类。


0

进一步的考虑是,密封类不能在单元测试中存根。从Microsoft的文档中

密封类或静态方法不能存根,因为存根类型依赖于虚拟方法分派。对于这种情况,请使用使用垫片中所述的垫片类型将应用程序与其他程序集隔离以进行单元测试

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.