尝试理解静态构造函数

本文关键字:静态 构造函数 | 更新日期: 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()
    {
    }
}

AB 不能保证是相同的值,但如果要在构造函数中编写它,则可以保证它:

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之前分配。在此示例中,A1B2 。我们可以保证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()