密封类与密封方法
本教程共 100 篇 · 第 46 篇 · 更新于 2026-07-31 · 约 10 分钟阅读
46. 密封类与密封方法
本节目标:用
sealed禁止类被继承或方法被重写,理解它适合的场景。
sealed 是什么
sealed 意为”密封”。修饰类时,这个类不能再被别人继承;修饰方法时,这个被重写的方法不能再被下一代改写。它是 virtual/override 的反面,用来”封死扩展点”。
可以把它想成工厂流水线上的封条:产品到了这一站就封箱,后面不许再拆开改装。继承让我们自由扩展,但有时自由反而带来风险。sealed 就是一道”到此为止”的边界,它告诉后来的开发者:这一层就是终点,别再往下改。
密封类
给类加 sealed,任何想继承它的尝试都会编译失败。
namespace CSharpDemo;
sealed class Logger
{
public void Write(string msg) => Console.WriteLine(msg);
}
Logger log = new();
log.Write("记录一条日志");
// 下面这行会报错:无法从密封类继承
// class FileLogger : Logger { }
静态类(static class)天生就是密封的,你本就继承不了它。所以给静态类再加 sealed 是多余的,编译器虽不报错,但毫无意义。
密封方法
在 override 前加 sealed,可阻止再往下的派生类继续改写该方法(见第 41 章方法重写)。
namespace CSharpDemo;
class Animal
{
public virtual string Sound() => "动物叫";
}
class Dog : Animal
{
public sealed override string Sound() => "汪汪";
}
// 下面这行会报错:无法再重写 Dog.Sound
// class Bulldog : Dog { public override string Sound() => "呼噜"; }
Dog 之后任何类继承 Dog,都不能再改 Sound。注意:密封方法必须写在 override 上,不能单独修饰普通虚方法。语法要求”先重写、再封死”——如果父类方法没标 virtual,你根本没法 override,自然也谈不上 sealed override。
为什么这样设计
sealed 的存在理由主要有三个,理解它们才不会”为了用而用”。
第一是安全。某些类的逻辑一旦被篡改会出大问题(如加密、权限校验、金额计算)。封死继承,别人就无法派生子类覆盖关键行为。
第二是性能。虚方法调用要在运行期查表分派(virtual dispatch)。JIT 编译器遇到 sealed 类或方法,可以做”去虚拟化(devirtualization)“,直接内联调用,少一次查表。对高频调用的小类型,这点开销值得省。
第三是设计意图明确。当你确认”这个类型不该被扩展”,主动 sealed,等于在代码里写下一句文档:别从这里派生。
Warning别把
sealed当”私有”用。它只管能不能继承,完全不管成员的可见性。想隐藏实现请用private/protected。两者管的事完全不同,混淆会带来错误的安全感。
什么时候该用 sealed
遇到下面几类情况,主动密封是负责任的选择:
- 安全敏感类型:加密、鉴权、签名这类逻辑,一旦能被子类覆盖就可能被绕过。封死继承是最省心的防护。
- 行为已完整、无需扩展:工具类、常量容器、固定算法实现,本就不该被改。
- 性能热点上的小类型:高频调用的叶子类,密封后能帮 JIT 做去虚拟化,调用更快。
- 明确不希望被误用的基类:你清楚任何”扩展”都会破坏不变式(invariant),那就提前封死。
sealed 与抽象类、接口的对比
| 维度 | sealed | abstract | 接口 interface |
|---|---|---|---|
| 能否被继承 | 不能 | 必须被继承 | 可被实现 |
| 能否实例化 | 能(类本身) | 不能 | 不能 |
| 设计意图 | 封死扩展 | 强制补完 | 定义契约 |
| 能否共存 | 与 abstract 互斥 | 与 sealed 互斥 | 可独立存在 |
一个类不可能既是密封又是抽象,逻辑上自相矛盾。一个阻止继承,一个强制继承,方向相反。接口则走另一条路:它不阻止继承,只规定”必须实现什么”。
Tip默认不密封。只有当”被继承会带来真实风险或混乱”时,才主动密封。把
sealed当作设计声明,而不是防御性过度约束。
什么时候不该用 sealed
反过来说,以下情况请慎用 sealed:
- 你只是”暂时没想到”别人要扩展,并不代表永远不需要。过度密封会逼别人复制你的代码。
- 你在写一个可能被他人复用的库,过早封死会限制使用方的合理定制。
- 类型本身很小、调用不频繁,性能收益可以忽略,密封只会增加认知负担。
Note经验法则:
sealed是”收紧”,请慎用。先假设类型可被继承,直到你有明确理由封死它。库作者尤其要克制,因为封死的扩展点是再也打不开的。
新手常见坑
第一,把 sealed 当”私有”用。它只管能不能继承/重写,不管成员可见性,二者是两回事。
第二,过度密封导致别人无法在合理处扩展,反而要绕路复制代码。密封是”收紧”,请慎用。
第三,结构 struct 本就不能被继承,给 struct 加 sealed 是多余的。
第四,以为密封方法能让整个类不可继承。它只封死那一个方法,类照样能被继承,只是该方法不能再被覆盖。
第五,在密封方法上又试图 virtual。sealed 与 virtual 不能同时修饰同一成员,二者语义直接冲突。
常见疑问解答
问:密封类还能被实例化吗?
能。sealed 只禁止”被继承”,不影响 new 创建对象。工具类、日志类经常既是 sealed 又能正常 new。
问:sealed 能修饰字段或属性吗?
不能。它只能修饰类,以及在 override 上的方法/属性。想锁住字段请用 readonly,锁住属性赋值请用 init。
问:接口里的默认方法能 sealed 吗?
可以。在接口中用 sealed override 能阻止实现类再改这个默认实现(见第 45 章)。这是 sealed 在接口维度的延伸。
问:我该默认把所有类都 sealed 吗? 不。默认开放继承,只在有充分理由时封死。盲目全密封会让代码僵化,别人想扩展只能复制,反而更糟。
问:密封方法和私有方法有何不同? 私有方法是”外面根本看不到”,密封方法是”看得到、能调用,但不能再重写”。一个是可见性,一个是可重写性,处在不同层面。
小结
密封是面向对象里”收口”的工具:该开放时开放,该封死时封死。sealed 用得克制而精准,代码既安全又高效。
讲完继承与多态的开放,我们回到底层:所有类型共同的祖宗 object。