静态字段稍后在类具有静态构造函数时初始化

本文关键字:静态 构造函数 初始化 字段 | 更新日期: 2023-09-27 18:21:51

通过运行以下简单代码:

class Program
{
    class MyClassWithStatic
    {
        public static int Number = SomeService.GetData();
        static MyClassWithStatic()
        {
            Console.WriteLine("static ctor runs");
        }
    }
    class SomeService
    {
        public static int GetData()
        {
            Console.WriteLine("GetDataRuns");
            return 42;
        }
    }        
    static void Main(string[] args)
    {
        InitService();
        var value = MyClassWithStatic.Number;
        Console.WriteLine(value);
    }
    private static void InitService()
    {
        Console.WriteLine("InitServiceRuns");
    }
}

我机器上的输出是这样的:

InitServiceRuns
GetDataRuns
静态ctor运行
42

这意味着首先调用InitService方法,然后初始化MyClassWithStatic的静态字段,然后调用静态构造函数(事实上,通过在ILSpy和IlDasm中查看这一点,我们可以看到静态字段的初始化发生在cctor的开头)

在这一点上没有什么有趣的,一切都有意义,但当我删除MyClassWithStatic的静态构造函数时(所以MyClassWithStatic变成了这个,其他一切都保持原样)

class MyClassWithStatic
{
    public static int Number = SomeService.GetData();
}

输出是这样的:

GetDataRuns
InitServiceRuns

这意味着通过移除静态构造函数,可以更早地初始化静态字段。由于初始化是静态构造函数的一部分(我通过ildasm研究它来说明这一点),因此效果基本上是静态构造函数被更早地调用。

所以问题来了:

  1. 有人能解释这种行为吗?这可能是什么原因?

  2. 当调用静态构造函数时,还有其他事情可以改变吗?(例如,附加探查器或在IIS中运行它,等等)(我比较了调试、发布模式、x86、x64,所有这些都显示出相同的行为)

一些一般的东西:

-这是在.NET 4.6控制台应用程序中。我还切换到了.NET2(应该使用不同的clr运行,行为是一样的,没有任何区别)

-我也在.NET核心中尝试过:无论有没有cctor,都会首先调用InitService方法。

-现在我完全知道这个页面:

用户无法控制何时在中执行静态构造函数程序。

我也知道,在静态构造函数中,有很多事情是不应该做的。但不幸的是,我不得不处理一个代码,其中的这一部分超出了我的控制范围,我所描述的差异产生了巨大的影响。(我还浏览了许多与C#cctor相关的SO问题。)

(问题3:)那么,我描述的整件事不是有点问题吗?

静态字段稍后在类具有静态构造函数时初始化

具有静态构造函数的类将不会标记有beforefieldinit标志,这允许运行时稍后对其进行初始化(换句话说,MyClassWithStatic.Number将在首次引用/访问MyClassWithStatic时进行初始化)

点击本文获取更多信息。

有人能解释这种行为吗?这可能是什么原因?

@JonSkeet在C#in Depth中有一段关于静态字段和静态构造函数的内容。这里有一个片段:

C#规范规定:

  • 类的静态构造函数在给定的应用程序域。静态构造函数的执行被触发由应用程序中发生的下列事件中的第一个域:

    • 将创建该类的实例
    • 的任何静态成员类被引用

CLI规范(ECMA 335)在第8.9.5节:

类型可能有类型初始值设定项方法,也可能没有。一种类型可以是指定为其类型初始值设定项方法具有松弛语义(为了方便起见,我们将这种轻松的语义称为BeforeFieldInit):

  • 如果标记为BeforeFieldInit,则该类型的初始值设定项方法为在首次访问任何静态字段时或之前执行为该类型定义
  • 如果未标记为BeforeFieldInit,则该类型初始化器方法执行(在(即由触发):first访问该类型的任何静态或实例字段,或者首先调用该类型的任何静态、实例或虚拟方法)

这将向您表明,当一个类型没有beforefieldinit标志时,运行时可以在任意时间调用它,前提是它在第一次访问定义的任何静态字段之前,这正是您所看到的。

当静态构造函数被称为?

唯一需要做的就是在类型上创建一个静态类型构造函数。否则,你无法控制它的调用。

那么,我描述的整件事不是有点问题吗?

在哪些方面有问题?只要你知道自己在干什么,我看没问题。CLI规范非常清楚地说明了使用类型初始值设定项和不使用类型初始设定项可以保证什么。因此,如果你遵循这些准则,就不应该有歧义。