一、简介
在 C# 中,sealed 关键字用于定义一个密封类或者密封方法,也就是不能被继承的类。这样做的目的是为了限制类的继承,确保类的行为和实现不会被改变。
此外,密封类也有助于提升代码的安全性和可维护性。由于密封类不能被继承,开发者可以更好地控制类的行为,避免不必要的继承和重写带来的风险。
**sealed**类是 C# 提供的一个强大工具,它帮助开发者更好地控制类的设计和行为。
(过多基础知识介绍就不在此赘述了,今天探讨的问题点是 sealed密封类是否会带来性能上的提升,这是一个非常有意思的话题。 )
二、类从元数据到 CLR 运行时
1) 编译器输出:源代码 → IL + 元数据(Assembly / PE)
C#语言被编译成 IL(中间语言)并写入 PE(Portable Executable)文件中。同时生成 元数据(Metadata):类型定义(TypeDef)、字段(FieldDef)、方法(MethodDef)、参数、属性、接口实现、常量、引用到其他模块(TypeRef/MemberRef/AssemblyRef)等。
1.1)Roslyn
Roslyn简介:Roslyn = C# / VB 的"编译器 + 代码分析框架",而且本身就是用 C# 写的。在 .NET 中它不仅负责把代码编译成 IL,还把"编译过程本身"开放出来让你可以读语法树、编辑语法树、生成代码、做分析、做重写。
在 Roslyn / 编译器语境里:**使用编译器自身的能力来编译或生成编译器本身。**这种方式称之为自举。自举的意义在于见证语言的完整性、减少依赖。
1.2)核心类MetadataWriter
1.2.1)PopulateTypeDefTableRows
PopulateTypeDefTableRows方法会把中所有的类型写入 TypeDef 表 ,并为每个类型生成 Metadata 所需的行数据。(MetadataWriter还包含很多方法,PopulateTypeDefTableRows只会把类型写入TypeDef 类型引用表中。还有其他的PopulateMethodDefTableRows()、PopulateCustomAttributeTableRows()等方法,会写入对应的方法和特性引用表中,最终这些引用表合成Metadata。就不在此详细讲解了。因为实在是有太多东西了。。。。。。)

1.2.2)WriteMetadataAndIL
WriteMetadataAndIL会构建所有生成的Metadata,通过emit生成IL。把Metadata和IL拼成最终的二进制块,交给 PEWriter 写入 PE 文件。最终我们将得到.dll或者.exe文件。

2)方法引用的内联和展开
2.1)内联(inlining)
内联:方法调用在最终生成的 IL 或 JIT 机器码中直接替换成方法体的代码,不再有调用指令。常用于小方法,提高性能。
2.2)展开(普通调用 / 不内联)
调用保留 call 指令,执行时需要跳转到被调用方法,维护调用栈和参数。
2.3)call和callvirt
call:编译期直接确定方法地址,调用点直接跳转到方法体,不需要虚表查找。
callvirt:编译期只知道方法 slot(在 vtable 或接口表),运行时根据对象类型查找实际实现。
2.4)抽象类抽象方法
我们来写一个小的demo:
cs
namespace ConsoleApp1
{
public abstract class AbstractTest
{
public abstract void Test();
}
class AbstractClass : AbstractTest
{
public override void Test()
{
Console.WriteLine("Hi");
}
}
public class ConsoleWriteLine
{
void Test(AbstractClass abs)
{
abs.Test();
}
}
}
通过ILSpy我们可以看到,ConsoleWriteLine这个类在调用抽象方法时使用的是callvirt。而callvirt会先去vtable表中去检索,找到虚方法或者抽象方法的具体实现地址在哪里。

3)sealed密封类
3.1)虚拟化**(Virtualization)**
虚拟化:在 C# / CLR 里主要指方法调用的多态机制,也就是 运行时动态分派(dynamic dispatch),允许子类重写父类方法。让调用者在编译期无法确定具体方法体,而是在运行时根据对象类型决定调用哪个实现。
虚拟化的实现方法是-->虚方法表(vtable)+ callvirt 指令。关键目的是支持多态。
3.2)去虚拟化(Devirtualization)
如果对象类型在运行期唯一可确定,例如 sealed 类-->JIT 可以去掉虚拟化机制。这样对比虚拟化的优势是,不在进行vtable表检索,减少调用性能下降。
4)RunTime中的sealed
4.1)RunTime

我们可以看到Runtime会把sealed密封类标记为 "CORINFO_FLG_FINAL"

检测到是:sealed密封类;static静态类;不是抽象类,就会停止检索。Runtime JIT会对其进行优化。