297 lines
5.4 KiB
Markdown
297 lines
5.4 KiB
Markdown
|
|
# 密封函数
|
||
|
|
概念:
|
||
|
|
密封函数指的是使用 `sealed` 修饰的==重写成员==
|
||
|
|
在 C# 里常见写法是 `sealed override`
|
||
|
|
|
||
|
|
准确一点说:
|
||
|
|
C# 里通常叫“密封方法”或“密封重写成员”
|
||
|
|
有些教程会说“密封函数”,初学阶段可以理解成:==不允许下一级子类继续重写的方法==
|
||
|
|
|
||
|
|
通常用法:
|
||
|
|
1. 父类允许子类重写某个方法
|
||
|
|
2. 某个子类重写后,认为这个实现已经定死
|
||
|
|
3. 允许这个子类继续被继承
|
||
|
|
4. 但不允许更下一级子类再改这个方法
|
||
|
|
|
||
|
|
----
|
||
|
|
# 语法
|
||
|
|
```csharp
|
||
|
|
class 子类名 : 父类名
|
||
|
|
{
|
||
|
|
public sealed override void 方法名()
|
||
|
|
{
|
||
|
|
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
注意:
|
||
|
|
`sealed` 修饰方法时,必须和 `override` 一起使用
|
||
|
|
|
||
|
|
----
|
||
|
|
# 基础案例
|
||
|
|
```csharp
|
||
|
|
class Animal
|
||
|
|
{
|
||
|
|
public virtual void Speak()
|
||
|
|
{
|
||
|
|
Console.WriteLine("动物叫");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
class Dog : Animal
|
||
|
|
{
|
||
|
|
public sealed override void Speak()
|
||
|
|
{
|
||
|
|
Console.WriteLine("狗叫");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
class Husky : Dog
|
||
|
|
{
|
||
|
|
// 这里不能再 override Speak()
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
理解:
|
||
|
|
1. `Animal.Speak()` 是虚方法,允许被重写
|
||
|
|
2. `Dog.Speak()` 重写了父类方法
|
||
|
|
3. `Dog.Speak()` 又被 `sealed` 密封
|
||
|
|
4. `Husky` 可以继承 `Dog`
|
||
|
|
5. 但 `Husky` 不能继续重写 `Speak()`
|
||
|
|
|
||
|
|
----
|
||
|
|
# sealed class 和 sealed override 的区别
|
||
|
|
密封类:
|
||
|
|
```csharp
|
||
|
|
sealed class Dog : Animal
|
||
|
|
{
|
||
|
|
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
含义:
|
||
|
|
整个 `Dog` 类不能再被继承
|
||
|
|
|
||
|
|
密封函数:
|
||
|
|
```csharp
|
||
|
|
class Dog : Animal
|
||
|
|
{
|
||
|
|
public sealed override void Speak()
|
||
|
|
{
|
||
|
|
Console.WriteLine("狗叫");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
含义:
|
||
|
|
`Dog` 类仍然可以被继承
|
||
|
|
但是 `Speak()` 不能再被下一级子类重写
|
||
|
|
|
||
|
|
对比:
|
||
|
|
1. `sealed class`:封住整个类
|
||
|
|
2. `sealed override`:只封住某个已经重写的成员
|
||
|
|
|
||
|
|
这个点可以和[[密封类]]一起记。
|
||
|
|
|
||
|
|
----
|
||
|
|
# 为什么密封函数必须是 override
|
||
|
|
错误写法:
|
||
|
|
```csharp
|
||
|
|
class Dog
|
||
|
|
{
|
||
|
|
public sealed void Speak()
|
||
|
|
{
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
原因:
|
||
|
|
普通方法本来就不能被 `override`
|
||
|
|
没有必要再用 `sealed` 封住
|
||
|
|
|
||
|
|
正确理解:
|
||
|
|
`sealed` 修饰方法时,是在说:
|
||
|
|
“这个方法原本处在可重写链条上,但从我这里开始不准继续重写”
|
||
|
|
|
||
|
|
所以它必须修饰已经重写的成员,也就是 `sealed override`。
|
||
|
|
|
||
|
|
----
|
||
|
|
# 多级继承案例
|
||
|
|
```csharp
|
||
|
|
class A
|
||
|
|
{
|
||
|
|
public virtual void Test()
|
||
|
|
{
|
||
|
|
Console.WriteLine("A");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
class B : A
|
||
|
|
{
|
||
|
|
public override void Test()
|
||
|
|
{
|
||
|
|
Console.WriteLine("B");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
class C : B
|
||
|
|
{
|
||
|
|
public sealed override void Test()
|
||
|
|
{
|
||
|
|
Console.WriteLine("C");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
class D : C
|
||
|
|
{
|
||
|
|
// public override void Test()
|
||
|
|
// {
|
||
|
|
// Console.WriteLine("D");
|
||
|
|
// }
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
调用:
|
||
|
|
```csharp
|
||
|
|
A a = new C();
|
||
|
|
a.Test(); // C
|
||
|
|
```
|
||
|
|
|
||
|
|
理解:
|
||
|
|
1. `A.Test()` 开启虚方法
|
||
|
|
2. `B.Test()` 继续重写
|
||
|
|
3. `C.Test()` 重写并密封
|
||
|
|
4. `D` 仍然可以继承 `C`
|
||
|
|
5. 但 `D` 不能再重写 `Test()`
|
||
|
|
|
||
|
|
----
|
||
|
|
# 密封属性
|
||
|
|
`sealed override` 不只可以用于方法,也可以用于属性。
|
||
|
|
|
||
|
|
```csharp
|
||
|
|
class Character
|
||
|
|
{
|
||
|
|
public virtual int MaxHp
|
||
|
|
{
|
||
|
|
get { return 100; }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
class Boss : Character
|
||
|
|
{
|
||
|
|
public sealed override int MaxHp
|
||
|
|
{
|
||
|
|
get { return 1000; }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
后续子类不能再重写 `MaxHp`。
|
||
|
|
|
||
|
|
----
|
||
|
|
# 和多态的关系
|
||
|
|
密封函数不会取消已有多态效果。
|
||
|
|
|
||
|
|
```csharp
|
||
|
|
Animal animal = new Dog();
|
||
|
|
animal.Speak(); // 狗叫
|
||
|
|
```
|
||
|
|
|
||
|
|
`Dog.Speak()` 仍然会通过多态被调用。
|
||
|
|
|
||
|
|
`sealed override` 只是限制:
|
||
|
|
后续子类不能继续改这个方法
|
||
|
|
|
||
|
|
也就是说:
|
||
|
|
1. 对上层父类来说,它仍然是重写后的多态方法
|
||
|
|
2. 对下层子类来说,它已经不能再被重写
|
||
|
|
|
||
|
|
----
|
||
|
|
# 常见错误
|
||
|
|
错误1:普通方法前写 `sealed`
|
||
|
|
```csharp
|
||
|
|
class A
|
||
|
|
{
|
||
|
|
public sealed void Test()
|
||
|
|
{
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
`sealed` 不能这样修饰普通方法。
|
||
|
|
|
||
|
|
----
|
||
|
|
错误2:只写 `sealed` 不写 `override`
|
||
|
|
```csharp
|
||
|
|
class B : A
|
||
|
|
{
|
||
|
|
public sealed void Test()
|
||
|
|
{
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
密封重写成员必须写成 `sealed override`。
|
||
|
|
|
||
|
|
----
|
||
|
|
错误3:父类方法不是虚方法
|
||
|
|
```csharp
|
||
|
|
class A
|
||
|
|
{
|
||
|
|
public void Test()
|
||
|
|
{
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
class B : A
|
||
|
|
{
|
||
|
|
public sealed override void Test()
|
||
|
|
{
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
父类方法不是 `virtual` / `abstract` / `override`,子类不能 `override`,也就不能 `sealed override`。
|
||
|
|
|
||
|
|
----
|
||
|
|
# 什么时候用密封函数
|
||
|
|
适合用的情况:
|
||
|
|
1. 某个重写逻辑非常关键,不希望后续子类改掉
|
||
|
|
2. 父类设计允许多态,但某一层子类希望终止这个方法的重写链
|
||
|
|
3. 框架或基础类库中需要保证关键行为稳定
|
||
|
|
|
||
|
|
初学阶段:
|
||
|
|
先知道这个语法和作用即可
|
||
|
|
自己写练习代码时用得不多
|
||
|
|
|
||
|
|
----
|
||
|
|
# 注意
|
||
|
|
1. 密封函数常见写法是 `sealed override`
|
||
|
|
2. 它只能用于已经重写的成员
|
||
|
|
3. 它阻止下一级子类继续重写这个成员
|
||
|
|
4. 它不会阻止当前类被继承
|
||
|
|
5. 如果想阻止整个类被继承,用 `sealed class`
|
||
|
|
6. 方法、属性、索引器、事件等重写成员都可以理解这个规则
|
||
|
|
|
||
|
|
----
|
||
|
|
# 一句话记忆
|
||
|
|
==sealed override 表示这个重写到我这里为止,后面的子类不能再改。==
|
||
|
|
|
||
|
|
----
|
||
|
|
# 面试/复习时怎么说
|
||
|
|
可以这样答:
|
||
|
|
|
||
|
|
“密封函数通常指 `sealed override` 修饰的重写方法。
|
||
|
|
它表示当前类重写了父类的虚方法,但不允许更下一级子类继续重写。
|
||
|
|
它和密封类不同,密封类是不允许整个类被继承,而密封函数只是禁止某个重写成员继续被重写。”
|
||
|
|
|
||
|
|
----
|
||
|
|
引用:
|
||
|
|
1. Microsoft Learn: [sealed (C# Reference)](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/sealed)
|
||
|
|
2. Microsoft Learn: [override (C# Reference)](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/override)
|
||
|
|
|
||
|
|
#密封函数
|
||
|
|
#密封方法
|
||
|
|
#核心
|