静态构造函数在类型首次被主动引用时执行且仅一次,触发场景包括访问静态成员、创建首个实例或反射触及类型元数据;typeof、数组长度获取及未赋值声明均不触发。

静态构造函数的触发时机
静态构造函数在类型首次被“主动引用”时执行,且仅执行一次。它不是在类定义时、也不是在第一次 new 实例时才触发——而是取决于 CLR 对该类型的首次“使用需求”。
常见触发场景包括:
- 访问该类型的任意静态成员(
static字段、属性、方法) - 创建该类型的第一个实例(即
new MyClass()) - 反射中调用
Type.GetMethod(...).Invoke(...)等首次触及该类型元数据的操作(但仅当该操作导致类型初始化时)
注意:typeof(MyClass)、MyClass[].Length(数组类型本身不触发)、或仅声明 MyClass obj;(未赋值)均不会触发静态构造函数。
为什么有时看起来没执行?
最常见原因是误判了“首次使用点”。比如下面这段代码:
class Program
{
static void Main()
{
Console.WriteLine("Start");
// 此处尚未访问 TestClass 的任何成员
Console.WriteLine("End");
}
}
class TestClass
{
static TestClass()
{
Console.WriteLine("Static ctor fired!");
}
}
输出只有 Start 和 End——因为 TestClass 根本没被用到。CLR 不会为未引用的类型运行静态构造函数。
另一个典型陷阱是:在静态字段初始化器中引用了其他类,而那个类的静态构造函数又反过来依赖当前类——这会导致 TypeInitializationException,且异常堆栈里可能只显示“无法初始化类型”,不容易定位循环依赖。
与实例构造函数、static 字段初始化的顺序
静态构造函数的执行时机严格遵循以下顺序:
- 所有
static字段按声明顺序初始化(字面值或=表达式) - 然后才执行静态构造函数体(
static TestClass()中的代码) - 最后才是实例构造函数(每次
new时)
例如:
class TestClass
{
static int a = Console.WriteLine("a init") == 0 ? 1 : 0; // 先执行
static TestClass()
{
Console.WriteLine("static ctor"); // 后执行
}
}
输出一定是:a init → static ctor。如果字段初始化表达式里抛出异常,静态构造函数根本不会进入。
不能显式调用,也不能带参数或访问修饰符
静态构造函数语法受限极严:
- 不能加
public/private等修饰符(编译报错 CS0515) - 不能有参数(否则编译报错 CS0137)
- 不能被手动调用(如
TestClass.ctor()是非法语法) - 不能被继承或重写(子类有自己独立的静态构造函数)
如果你需要“可控的静态初始化”,得改用显式方法 + 静态布尔标记,例如:
class TestClass
{
private static bool _initialized;
private static readonly object _lock = new object();
public static void EnsureInitialized()
{
if (!_initialized)
{
lock (_lock)
{
if (!_initialized)
{
// 手动初始化逻辑
_initialized = true;
}
}
}
}
}
这种模式绕过了 CLR 的自动触发机制,适合需要延迟、条件化或可重入初始化的场景。
真正容易被忽略的是:静态构造函数的线程安全性由 CLR 保证(自动加锁),但它的执行时机不可控——一旦类型被 JIT 或反射提前加载,就可能在你完全没预料的上下文里跑起来,比如在某个后台线程、甚至 ASP.NET Core 请求管道早期阶段。调试时看不到调用栈,只能靠日志或断点确认是否已触发。








