稍后当类具有静态构造函数

时间:2015-11-14 14:00:18

标签: c# .net static clr coreclr

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

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中查看这个,我们可以看到静态字段的初始化发生在主持人的开头)

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

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

输出是这样的:

  

GetDataRuns
InitServiceRuns
42

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

以下是问题:

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

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

  3. 一些一般性事项:

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

    - 我也尝试使用.NET内核:无论是否使用cctor,首先调用InitService方法。

    - 我完全清楚this page

      

    用户无法控制何时执行静态构造函数   该计划。

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

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

2 个答案:

答案 0 :(得分:3)

具有静态构造函数的类不会标记beforefieldinit标志,这允许运行时稍后初始化它(换句话说,MyClassWithStatic.Number将在您第一次引用时初始化/ access MyClassWithStatic

this article获取战利品以获取更多信息。

答案 1 :(得分:2)

  

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

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

  

C#规范声明:

     
      
  • 类的静态构造函数在给定的时间内最多执行一次   应用领域。触发静态构造函数的执行   在应用程序中发生以下第一个事件   域:

         
        
    • 创建了一个类的实例。
    •   
    • 任何静态成员   类被引用。
    •   
  •   
     

CLI规范(ECMA 335)声明   第8.9.5节:

     

类型可以具有类型初始化方法。一种类型可能是   指定为其类型初始化方法具有宽松语义   (为方便起见,我们称之为宽松的语义BeforeFieldInit):

     
      
  • 如果标记为BeforeFieldInit,那么类型的初始化方法是   在第一次访问任何静态字段之前或之前执行   为该类型定义
  •   
  • 如果没有标记为BeforeFieldInit,则表示该类型   初始化方法执行((在(即,由...触发):first   访问该类型的任何静态或实例字段,或者首先访问   调用该类型的任何静态,实例或虚拟方法))
  •   

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

  

静态构造函数是否还有其他可以改变的东西   被称为?

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

  

所以我描述的整个事情有点不成问题吗?

有问题的是什么?只要你知道自己在做什么,我就没有问题。 CLI规范使得对类型初始化程序的保证非常清楚。因此,如果你遵循这些指导原则,就不应该含糊不清。