请描述 C# 中 CLR 加载类型的完整过程,包括从程序集元数据解析到类型初始化的各个阶段。
考察说明
考查对 CLR 类型加载机制和底层原理的理解。
回答思路
- 【回答框架 1】类型加载始于程序集被加载到 AppDomain 时,CLR 通过元数据中的 TypeDef、MethodDef 等表定位类型定义。加载类型时会先创建 MethodTable 和 EEClass,MethodTable 存放类型的方法表、接口映射和字段布局信息,EEClass 则包含类型的基本定义数据。
- 【回答框架 2】类型加载过程遵循静态构造函数规则,在第一次访问类型的静态成员或实例之前,CLR 会执行 .cctor。CLR 使用 BeforeFieldInit 标记优化执行时机,如果类型没有显式静态构造函数,则可能在首次访问静态字段之前执行。
- 【回答框架 3】加载过程中还需要解析基类型和接口,如果基类型未加载则递归加载。CLR 会通过类加载器(ClassLoader)在 AppDomain 中查找程序集,并使用融合日志绑定(Fusion)或 AssemblyLoadContext 解析依赖。
- 【回答框架 4】类型加载完成后,CLR 会进行 JIT 编译必要的方法,但并非所有方法都会立即编译。方法表项指向未编译代码的桩(stub),首次调用时触发 JIT。此外,CLR 还使用类型句柄(TypeHandle)和加载器堆(Loader Heap)来管理已加载类型的生命周期。
- 【关键点 1】类型加载基于程序集元数据,核心是创建 MethodTable 和 EEClass。
- 【关键点 2】静态构造函数(.cctor)在首次访问类型成员前执行,BeforeFieldInit 可优化执行时机。
- 【关键点 3】类型解析递归处理基类和接口依赖,使用 AssemblyLoadContext 加载程序集。
- 【关键点 4】JIT 编译是惰性的,首次调用方法时才触发。
- 【易错点 1】容易混淆类型加载和程序集加载,实际上每个程序集可能有多个类型,类型加载发生在程序集加载之后。
- 【易错点 2】静态构造函数中的异常会导致类型初始化失败,后续所有访问都会抛出 TypeInitializationException,但这不是类型加载失败。
- 【易错点 3】CLR 2.0 之后的类型加载可能延迟到 JIT 编译时,不一定在程序集加载时立即完成。