static constructors和BeforeFieldInit?

时间:2012-10-10 09:46:26

标签: c# .net .net-4.0

  

如果类型没有静态构造函数,则将执行字段初始值设定项   就在使用类型之前 - 或者在任何时候,随心所欲   运行时

为什么这段代码:

void Main()
{ 
  "-------start-------".Dump();
   Test.EchoAndReturn("Hello");
  "-------end-------".Dump();

}

 class Test
{
    public static string x = EchoAndReturn ("a");
    public static string y = EchoAndReturn ("b");
    public static string EchoAndReturn (string s)
    {
        Console.WriteLine (s);
        return s;
    }
}

产量:

-------start-------
a
b
Hello
-------end-------

这段代码:

void Main()
{ 
  "-------start-------".Dump();
   var test=Test.x;
  "-------end-------".Dump();

}

产量

a
b
-------start-------
-------end-------

了解ab的顺序。但为什么处理static method 与<{1}}不同

我的意思是为什么 start end 行在静态方法与静态字段的不同位置?我的意思是 - 在这两种情况下,他都要初始化这些领域......为什么呢?

(我知道我可以添加静态ctor使它变得相同 - 但我问这个特殊情况。)

(p.s。转储()就像console.write一样)

3 个答案:

答案 0 :(得分:6)

释放JIT的行为(从4.0 IIRC开始)不运行静态初始化程序,除非您调用的方法触及静态字段。这可能意味着静态字段未初始化。如果我在调试器之外的发行版中运行你的第一个代码,我得到:

-------start-------
Hello
-------end-------

如果我在调试器附带(发布)或调试版本(附带或不附带调试器)的情况下运行它,我会得到:

-------start-------
a
b
Hello
-------end-------

到目前为止很有趣。为什么你得到:

a
b
-------start-------
-------end-------

看起来每个方法的JIT基本上负责在这种情况下运行静态构造函数。你可以通过添加:

来看到这一点
if(NeverTrue()) { // method that returns false
        "-------start-------".Dump();
        var test = Test.x;
        "-------end-------".Dump();
}

将打印(即使在没有调试器的情况下发布)

a
b

所以访问字段的可能性是关键。如果我们将Test.x更改为对无法访问字段(并删除NeverTrue()内容)的方法的调用,那么我们会得到无输出任何

所以:在CLI的某些版本中,静态初始化程序的执行可能会延迟到包含对任何字段的提及的方法的JIT步骤(它不会检查该字段是否具有初始化程序)。 / p>

我们甚至可以在不运行静态初始化程序的情况下创建对象实例,只要我们不接触静态字段:

 public Test()
 {
     a = "";
 }
 string a;

使用:

"-------start-------".Dump();
new Test();
"-------end-------".Dump();

只打印(发布,没有调试器):

-------start-------
-------end-------

无论其!我们不应该建立任何取决于这个时间的东西:

  • 它在.NET版本之间发生变化
  • 它可能会在平台(x86,x64,CF,SL,.NETCore等)之间发生变化
  • 它可以根据是否附加调试器以及它是否是调试/发布版本来改变

答案 1 :(得分:3)

不能保证调用静态构造函数的时间,因此对于程序来说,它就像C ++中的Undefined Behavior。没有人应该依赖静态构造函数调用的顺序。例如,如果您在发布时编译程序,您将看到在两种情况下同时调用静态counstructor。

答案 2 :(得分:1)

这是.NET 4.0

如果类型没有静态构造函数但是具有初始化的静态字段,则编译器会创建一个类型构造函数并将初始化放入其中。

class Test
    {
        public static string x = EchoAndReturn("a");
        public static string y = EchoAndReturn("b");
        public static string EchoAndReturn(string s)
        {
            Console.WriteLine(s);
            return s;
        }
    }

以下IL中的结果(仅限于cctor部分)

.method private hidebysig specialname rtspecialname static 
        void  .cctor() cil managed
{
  // Code size       31 (0x1f)
  .maxstack  8
  IL_0000:  ldstr      "a"
  IL_0005:  call       string ConsoleApplication1.Test::EchoAndReturn(string)
  IL_000a:  stsfld     string ConsoleApplication1.Test::x
  IL_000f:  ldstr      "b"
  IL_0014:  call       string ConsoleApplication1.Test::EchoAndReturn(string)
  IL_0019:  stsfld     string ConsoleApplication1.Test::y
  IL_001e:  ret
} // end of method Test::.cctor

另外,根据CLR via C#,JIT编译器事先检查每个方法,哪些类型有静态构造函数。如果尚未调用静态构造函数,则JIT编译器会调用它。

这可以解释两个代码片段之间的区别。

When the just-in-time (JIT) compiler is compiling a method,
     

它会看到代码中引用了哪些类型。如果任何类型定义   一个类型构造函数,JIT编译器检查类型的类型   已经为此AppDomain执行了构造函数。如果   构造函数从未执行过,JIT编译器发出一个调用   将构造函数输入到JIT编译器所在的本机代码中   发射。

@comment

如果将一个字段初始值设定项移动到用户定义的类型构造函数中,编译器也会将您在类级别初始化的另一个字段移动到类型构造函数中。

static Test()
{
  y = EchoAndReturn("b");
}

与上述IL相同的结果。所以你自己的类型构造函数和编译器生成的构造函数之间基本没有区别(反正只能有一个)。