当 class 具有静态构造函数时,稍后会初始化静态字段

Static field is initialized later when the class has a static constructor

通过 运行 宁这个简单的代码:

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
static ctor runs
42

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

此时没有什么有趣的,一切都有意义,但是当我删除 MyClassWithStatic 的静态构造函数时(所以 MyClassWithStatic 变成了这个,其他一切都像以前一样)

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

输出是这样的:

GetDataRuns
InitServiceRuns
42

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

所以问题来了:

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

  2. 在调用静态构造函数时还有什么可以改变的吗? (例如,在 IIS 中附加分析器或 运行 连接它等)(我比较了调试、发布模式、x86、x64 并且都显示相同的行为)

一些通用的东西:

-这是在 .NET 4.6 控制台应用程序中。我也切换到 .NET 2(应该 运行 使用不同的 clr,并且行为相同,没有任何区别)

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

-现在我完全知道 this page:

The user has no control on when the static constructor is executed in the program.

而且我也知道在静态构造函数中有很多事情是不应该做的。但不幸的是,我必须处理一个代码,其中这部分不在我的控制范围内,而且我描述的差异会产生巨大的差异。 (而且我还经历了许多 C# cctor 相关的 SO 问题..)

(还有问题 nr3:)所以我描述的整个事情是不是有点问题?

具有静态构造函数的class不会被标记为beforefieldinit标志,这允许运行时在稍后的时间初始化它(换句话说,MyClassWithStatic.Number将当你第一次 reference/access MyClassWithStatic)

时被初始化

this article 获取更多信息。

Can someone explain this behaviour? What can be the reason for this?

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

The C# specification states:

  • The static constructor for a class executes at most once in a given application domain. The execution of a static constructor is triggered by the first of the following events to occur within an application domain:

    • An instance of the class is created.
    • Any of the static members of the class are referenced.

The CLI specification (ECMA 335) states in section 8.9.5:

A type may have a type-initializer method, or not. A type may be specified as having a relaxed semantic for its type-initializer method (for convenience below, we call this relaxed semantic BeforeFieldInit):.

  • If marked BeforeFieldInit then the type's initializer method is executed at, or sometime before, first access to any static field defined for that type.
  • If not marked BeforeFieldInit then that type's initializer method is executed ((at (i.e., is triggered by): first access to any static or instance field of that type, or first invocation of any static, instance or virtual method of that type))

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

Is there any other thing which can change when the static constructor is called?

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

So isn't the whole thing I described a little bit problematic?

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