尝试理解静态构造函数
本文关键字:静态 构造函数 | 更新日期: 2023-09-27 18:31:57
我试图理解对静态构造函数的需求。我找到的信息都没有回答我有以下问题。你为什么要这样做
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;
}
}
与此相反
class SimpleClass
{
// Static variable that must be initialized at run time.
static readonly long baseline = DateTime.Now.Ticks;
// Static constructor is called at most one time, before any
// instance constructor is invoked or member is accessed.
//static SimpleClass()
//{
//}
}
?
这不是其他问题的欺骗,这是关于不接受参数的静态构造函数。
需求在某种程度上是显而易见的:您希望为静态成员执行的不仅仅是一些字段初始化。
从逻辑上讲,如果你有这个类:
class SimpleClass
{
// Static variable that must be initialized at run time.
static readonly long baseline = DateTime.Now.Ticks;
}
您可以重写它以获得相同的效果:
class SimpleClass
{
// Static variable that must be initialized at run time.
static readonly long baseline;
static SimpleClass () {
baseline = DateTime.Now.Ticks;
}
}
但相反,您可以在静态构造函数中做更多的事情,例如检查(使用反射)某些属性并向它们发出一些快速访问器/getter,或者只是简单地通知其他系统您的类型已创建,等等。
来自 Jeffrey Richter CLR 通过 C# 书:
显然,幕当 C# 编译器看到具有使用内联的静态字段的类时 初始化(BeforeFieldInit 类),编译器发出 类的类型定义表条目,其中包含 BeforeFieldInit 元数据 旗。当 C# 编译器看到具有显式类型的类时 构造函数(精确类),编译器发出类的类型 不带 BeforeFieldInit 元数据标志的定义表条目。这 其背后的理由如下:静态字段的初始化 需要在访问字段之前完成,而显式 类型构造函数可以包含可观察的任意代码 副作用;此代码可能需要在精确的时间运行。
后发生的事情远不止这些,我建议您通过 C# 阅读 CLR 的整章:"类型构造函数"
这是可能的差异的示例:
class SimpleClass
{
static readonly long A = DateTime.Now.Ticks;
static readonly long B = DateTime.Now.Ticks;
static SimpleClass()
{
}
}
A
和 B
不能保证是相同的值,但如果要在构造函数中编写它,则可以保证它:
class SimpleClass
{
static readonly long A;
static readonly long B;
static SimpleClass()
{
var ticks = DateTime.Now.Ticks;
A = ticks;
B = ticks;
}
}
此外,顺序对于静态成员的实例化很重要。
根据 ECMA-334 关于静态字段初始化:
类声明的静态字段变量初始值设定项 对应于在 它们在类声明中出现的文本顺序。如果 静态构造函数 (§17.11) 存在于类中,执行 静态字段初始值设定项在执行该命令之前立即发生 静态构造函数。否则,静态字段初始值设定项为 在首次使用 该类的静态字段
所以,我们可以写这样的东西:
class SimpleClass
{
public static readonly long A = IdentityHelper.GetNext();
public static readonly long B = IdentityHelper.GetNext();
static SimpleClass()
{
}
}
public static class IdentityHelper
{
public static int previousIdentity = 0;
public static int GetNext()
{
return ++previousIdentity;
}
}
在这里,A
保证在B
之前分配。在此示例中,A
将1
,B
将2
。我们可以保证A
<B
(假设标识不会溢出并且线程没有问题)。现在,如果我们对字段重新排序:>
public static readonly long B = IdentityHelper.GetNext();
public static readonly long A = IdentityHelper.GetNext();
功能会更改。因此,我们创建了一个副作用,仅仅通过重新排序字段定义并不能立即清除。
更可能的情况是,我们可能想要这样做:
class SimpleClass
{
public static readonly long A = IdentityHelper.GetExpensiveResult().A;
public static readonly long B = IdentityHelper.GetExpensiveResult().B;
static SimpleClass()
{
}
}
在这里,我们无法在字段之间共享GetExpensiveResult()
。