静态构造函数(C# 编程指南)
静态构造函数用于初始化任何静态数据,或执行仅需执行一次的特定操作。 它会在创建第一个实例或引用任何静态成员之前自动调用。 静态构造函数最多调用一次。
class SimpleClass
{
// Static variable that must be initialized at run time.
static readonly long baseline;
// Static constructor is called at most one time, before any
// instance constructor is invoked or member is accessed.
static SimpleClass()
{
baseline = DateTime.Now.Ticks;
}
}
有多个操作在静态初始化时执行。 这些操作按以下顺序执行:
- 静态字段设置为 0。 通常由运行时执行此初始化。
- 静态字段初始值设定项运行。 派生程度最高类型的静态字段初始值设定项运行。
- 基类型静态字段初始值设定项运行。 以直接基开头从每个基类型到 System.Object 的静态字段初始值设定项。
- 所有静态构造函数运行。 任何静态构造函数都会运行,从 Object.Object 的最终基类到每一个基类,再到类型。 静态构造函数执行的顺序没有指定。 但是,在创建任何实例之前,层次结构中的所有静态构造函数都会运行。
重要
静态构造函数在创建任何实例之前运行的规则有一个重要例外。 如果静态字段初始值设定项创建了该类型的实例,那么该初始值设定项(包括对实例构造函数的任何调用)将在静态构造函数运行之前运行。 这在单一实例模式中最为常见,如以下示例所示:
public class Singleton
{
// Static field initializer calls instance constructor.
private static Singleton instance = new Singleton();
private Singleton()
{
Console.WriteLine("Executes before static constructor.");
}
static Singleton()
{
Console.WriteLine("Executes after instance constructor.");
}
public static Singleton Instance => instance;
}
模块初始化表达式可以替代静态构造函数。 有关详细信息,请参阅模块初始化表达式的规范。
备注
静态构造函数具有以下属性:
- 静态构造函数不使用访问修饰符或不具有参数。
- 类或结构只能有一个静态构造函数。
- 静态构造函数不能继承或重载。
- 静态构造函数不能直接调用,只能由公共语言运行时 (CLR) 调用。 它会自动调用。
- 用户无法控制在程序中执行静态构造函数的时间。
- 自动调用静态构造函数。 它在创建第一个实例或引用该类(不是其基类)中声明的任何静态成员之前初始化类。 静态构造函数在实例构造函数之前运行。 如果静态构造函数类中存在静态字段变量初始值设定项,它们将以在类声明中显示的文本顺序运行。 初始值设定项紧接着静态构造函数之前运行。
- 如果未提供静态构造函数来初始化静态字段,会将所有静态字段初始化为其默认值,如 C# 类型的默认值中所列。
- 如果静态构造函数引发异常,运行时不会再次调用该函数,并且类型在应用程序域的生存期内会保持未初始化状态。 大多数情况下,当静态构造函数无法实例化一个类型时,或者当静态构造函数中发生未经处理的异常时,将引发 TypeInitializationException 异常。 对于未在源代码中显式定义的静态构造函数,故障排除可能需要检查中间语言 (IL) 代码。
- 静态构造函数的存在将防止添加 BeforeFieldInit 类型属性。 这将限制运行时优化。
- 声明为
static readonly
的字段只能在其声明时或在静态构造函数中分配。 如果不需要显式静态构造函数,请在声明时初始化静态字段,而不是通过静态构造函数,以实现更好的运行时优化。 - 运行时在单个应用程序域中多次调用静态构造函数。 该调用是基于特定类型的类在锁定区域中进行的。 静态构造函数的主体中不需要额外的锁定机制。 若要避免死锁的风险,请勿阻止静态构造函数和初始值设定项中的当前线程。 例如,不要等待任务、线程、等待句柄或事件,不要获取锁定,也不要执行阻止并行操作,如并行循环、
Parallel.Invoke
和并行 LINQ 查询。
注意
尽管不可直接访问,但应记录显式静态构造函数的存在,以帮助故障排除初始化异常。
用法
- 静态构造函数的一种典型用法是在类使用日志文件且将构造函数用于将条目写入到此文件中时使用。
- 静态构造函数对于创建非托管代码的包装类也非常有用,这种情况下构造函数可调用
LoadLibrary
方法。 - 也可在静态构造函数中轻松地对无法在编译时通过类型参数约束检查的类型参数强制执行运行时检查。
示例
在此示例中,类 Bus
具有静态构造函数。 创建 Bus
的第一个实例 (bus1
) 时,将调用该静态构造函数,以便初始化类。 示例输出验证即使创建了两个 Bus
的实例,静态构造函数也仅运行一次,并且在实例构造函数运行前运行。
public class Bus
{
// Static variable used by all Bus instances.
// Represents the time the first bus of the day starts its route.
protected static readonly DateTime globalStartTime;
// Property for the number of each bus.
protected int RouteNumber { get; set; }
// Static constructor to initialize the static variable.
// It is invoked before the first instance constructor is run.
static Bus()
{
globalStartTime = DateTime.Now;
// The following statement produces the first line of output,
// and the line occurs only once.
Console.WriteLine("Static constructor sets global start time to {0}",
globalStartTime.ToLongTimeString());
}
// Instance constructor.
public Bus(int routeNum)
{
RouteNumber = routeNum;
Console.WriteLine("Bus #{0} is created.", RouteNumber);
}
// Instance method.
public void Drive()
{
TimeSpan elapsedTime = DateTime.Now - globalStartTime;
// For demonstration purposes we treat milliseconds as minutes to simulate
// actual bus times. Do not do this in your actual bus schedule program!
Console.WriteLine("{0} is starting its route {1:N2} minutes after global start time {2}.",
this.RouteNumber,
elapsedTime.Milliseconds,
globalStartTime.ToShortTimeString());
}
}
class TestBus
{
static void Main()
{
// The creation of this instance activates the static constructor.
Bus bus1 = new Bus(71);
// Create a second bus.
Bus bus2 = new Bus(72);
// Send bus1 on its way.
bus1.Drive();
// Wait for bus2 to warm up.
System.Threading.Thread.Sleep(25);
// Send bus2 on its way.
bus2.Drive();
// Keep the console window open in debug mode.
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Sample output:
Static constructor sets global start time to 3:57:08 PM.
Bus #71 is created.
Bus #72 is created.
71 is starting its route 6.00 minutes after global start time 3:57 PM.
72 is starting its route 31.00 minutes after global start time 3:57 PM.
*/