moduleinitializer不是比main更早执行的入口,仅保证在本程序集任意类型首次访问前运行;必须定义在非泛型、非嵌套的static class中,方法需为public或internal且无参数无返回值,项目须启用c# 9+。

ModuleInitializer 不是“比 Main 更早执行的入口”,它只保证在本程序集任意类型首次访问前运行,但不保证早于 Main —— 很多调试失败就栽在这里。
ModuleInitializer 方法必须定义在 static class 里,不能是顶级函数
网上不少示例写成“直接在命名空间下写 [ModuleInitializer] public static void Initialize()”,这是错的(C# 9+ 也不支持)。编译器会报 CS8893: A module initializer must be in a static class。
- 必须放在
static class内部,且该 class 不能是泛型、不能嵌套在其他类中 - 方法本身必须是
public或internal,private会静默失效(不报错但不执行) - 项目 SDK 必须启用 C# 9+,检查 .csproj 是否含
<langversion>9.0</langversion>或更高
多个 ModuleInitializer 的执行顺序不可靠,别互相依赖
同一程序集中可以有多个 [ModuleInitializer] 方法,但编译器生成的调用顺序取决于类型名和源码位置,运行时不做保证。你看到“先 A 后 B”只是当前编译结果,下次改个类名可能就反了。
- 不要在
InitializeA里读取InitializeB初始化的静态字段 - 跨程序集的初始化器更不可控 ——
Assembly.LoadFrom触发的模块加载,其初始化器执行时机与主程序集无同步保障 - 若需顺序,应合并为一个方法,或改用显式初始化入口(如
Startup.Init())
ModuleInitializer 里调用未 JIT 的类型会触发意外静态构造函数
看似安全的代码:Logger.Configure(...),如果 Logger 类还没被 JIT,它的静态构造函数就会在此刻被强制触发 —— 这可能导致死锁(比如静态构造函数又去等另一个尚未初始化的模块)或循环依赖。
- 只调用已明确“被引用过”的类型,例如在
Main中提前访问一次typeof(Logger)可确保其类型已加载 - 避免在初始化器中使用反射扫描类型(
Assembly.GetExecutingAssembly().GetTypes()),这会强制 JIT 所有类型 - 日志、配置、DI 容器注册等操作,优先用延迟初始化(
Lazy<t></t>)或接口抽象隔离
真正难处理的是跨模块依赖和 JIT 时序耦合——这两点不会报错,但会让初始化行为随编译环境、部署方式甚至 .NET 版本悄悄变化。










