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

3
通过运行这个简单的代码:
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 core:无论是否使用cctor,InitService方法都会首先被调用。

-我现在完全意识到这个页面:

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

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

(第三个问题:) 那么我所描述的整个情况不是有点棘手吗?


1
听起来像XY问题。你能描述下为什么想要控制它吗? - Hamid Pourjam
问题在第一条回答中有很好的解释,但你需要哪个方向的答案或解决方法?期望的行为是什么,你可以更改哪些类? - H H
实际情况是该类仅具有静态字段(=我的第二个示例),当分析器连接到它时,字段会更早地初始化。现在我不知道beforefieldinit标志,所以我尝试添加/删除cctor进行实验。我不想改变任何东西...我只想了解为什么。因此,我们可以得出结论,如果一个类型被标记为beforefieldinit,则连接分析器似乎会产生差异(这与文档完全同步...)。 - gregkalapos
2个回答

3
一个具有静态构造函数的类不会被标记为beforefieldinit标志,这允许运行时在稍后时间初始化它(换句话说,当您第一次引用/访问MyClassWithStatic时,MyClassWithStatic.Number将被初始化)。
阅读本文获取更多信息。

2
有人能解释一下这种行为吗?这可能是什么原因?
@JonSkeet在《深入C#》一书中有一个关于静态字段和静态构造函数的段落。以下是一小段摘录:
C# 规范声明:
类的静态构造函数在给定应用程序域中最多执行一次。触发静态构造函数的执行是由以下事件中的第一个事件在应用程序域内首先发生:
- 创建类的实例。 - 引用该类的任何静态成员。
CLI 规范(ECMA 335)在第8.9.5节中声明:
类型可以具有类型初始化器方法,也可以没有。类型可以被指定为具有其类型初始化器方法的放松语义(为方便起见,我们称之为 BeforeFieldInit):
- 如果标记为 BeforeFieldInit,则该类型的初始化器方法在或在访问该类型定义的任何静态字段的第一次访问之前执行。 - 如果未标记为 BeforeFieldInit,则该类型的初始化器方法在或在访问该类型的任何静态或实例字段的第一次访问或调用该类型的任何静态、实例或虚拟方法时执行。
这表明,当一个类型没有 beforefieldinit 标志时,运行时可能在任意时间调用它,只要它在定义的任何静态字段的第一次访问之前,这正是你所看到的。

还有什么其他因素会改变静态构造函数的调用时间吗?

唯一的因素就是在你的类型上创建静态类型构造函数。否则,你无法控制其调用。

那么我描述的整个过程不是有点问题吗?

在哪方面有问题?只要你知道你要做什么,我认为没有问题。CLI 规范非常清楚地说明了带有类型初始化器和不带有类型初始化器的保证。因此,如果你遵循这些指南,就不应该存在任何歧义。


太好了,谢谢!如您在我的评论中所见,在我的情况下,该类被标记为beforefieldinit...您知道在这种情况下,如果我附加一个分析器,初始化是否会更早发生?(因为这仍然与文档同步)。因此,基本上在理解这种情况之后,我的第二个问题变成了这个:“在标记为beforefieldinit的类的情况下,有什么可以改变类型初始化发生的东西(例如在IIS中运行,附加分析器等)?” - gregkalapos
@gregkalapos 不是的。据我所知,初始化的时间由运行时决定。 - Yuval Itzchakov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接