C#BeforeFieldInit Jon Skeet解释混乱

L. *_*rdt 8 c# initialization

我通读乔恩斯基特的文章有关beforefieldinit,我偶然发现了一个问题.他提到可以在调用静态字段的第一次引用之前的任何时间调用类型初始值设定项.

这是我的测试代码:

class Test1
{
    public static string x1 = EchoAndReturn1("Init x1");

    public static string EchoAndReturn1(string s)
    {
        Console.WriteLine(s);
        return s;
    }
}

class Programm
{
    public static void Main()
    {
        Console.WriteLine("Starting Main");

        Test1.EchoAndReturn1("Echo 1");

        Console.WriteLine("After echo");

        string y = Test1.x1;  //marked line
    }
}
Run Code Online (Sandbox Code Playgroud)

输出是:

Init x1
Starting Main
Echo 1
After echo
Run Code Online (Sandbox Code Playgroud)

但是没有标记的行,所以没有静态字段的调用x1,输出是:

Starting Main
Init x1
Echo 1
After echo
Run Code Online (Sandbox Code Playgroud)

因此,标记的对象beforefieldinit的调用会影响其类型初始值设定项的调用吗?或者这是否属于beforefieldinit他提到的奇怪效果?

因此,beforefieldinit可以使类型初始化程序的调用更加懒惰或更加渴望.

Eri*_*ert 20

我不确定这里有什么问题; 也许如果我解释一下抖动的概念,那就会回答这个问题.

具有显式静态构造函数的静态类具有关于何时运行cctor的"严格"语义:它在第一次使用该类型成员之前立即运行.所以,如果你有

if (whatever) x = Foo.Bar;
Run Code Online (Sandbox Code Playgroud)

Foo如果whatever为false,则不运行cctor for ,因为我们还没有遇到成员的实际使用.

想想这对于jitted代码意味着什么.如何为具有此要求的语言编写抖动

对于静态方法调用,您可以在每个调用站点放置一点前传,以检查cctor是否已运行.但这会使每个呼叫站点变得更大更慢.

您可以将前传放入静态方法本身.这会使呼叫站点保持较小,但每次呼叫仍会稍微变慢.

或者,你可以很聪明,并在静态方法第一次进行抖动时检查抖动.这样,您只需支付一次支票的费用,并且通话网站保持较小.jit成本变得更大,但只有很小一部分; jitting已经很贵了.

但请注意,这样做会排除导致方法在第一次调用之前被jitted的任何优化,因为这样的优化现在引入了正确性问题.优化几乎总是涉及权衡!

但是对于字段访问,没有方法可以进行jit.抖动必须在每次访问场地之前放一点前传,这可能是第一次.因此,访问字段不仅会变慢,而且代码也会变.

你可能会想为什么不把这个领域变成一个属性并把前传放在getter和setter的jitting上?,但这不起作用,因为字段是变量而属性不是.我们需要能够通过ref和传递静态字段out,但是你不能用属性来做.该字段可能是volatile,也可能不是属性.等等.

能够在现场访问中避免这些成本会很好.

没有显式cctor但使用编译器生成的隐式cctor来初始化静态字段的静态类获得"宽松"语义,其中抖动仅保证在访问字段之前的某个点调用cctor .您的程序使用这些轻松的语义.

在第一个版本中,通过字段访问,抖动从其对方法的分析中知道可以访问静态字段.(为什么"可能"?如前所述,访问可以在一个if.).允许抖动在第一次访问之前的任何时间运行cctor ,所以它做的是它做一个说明何时Main被jitted,检查到查看是否已运行Test1 cctor,如果没有,则运行它.

如果Main第二次被召唤,嘿,它只会被一次性击中.因此,支票的成本仅在第一次通话时承担.(当然Main在大多数程序中只被调用过一次,但如果你遇到那种东西,你可以写一个递归的Main.)

在您的第二个程序中没有字段访问权限.抖动也可能导致访问静态方法,并且可以在jit时间为Main运行cctor.它不是.为什么不?我不知道; 你不得不向抖动团队询问这个问题.但问题是,抖动完全在其使用启发式决定是否在jit时间运行cctor的权利之内,并且它确实如此.

抖动也在其使用启发式来决定是否调用接触无字段的静态方法触发cctor的权利; 在这种情况下显然它不必要地这样做.

你的问题似乎是"这些启发式是什么?" 答案是......好吧,我没有明确知道答案是什么,而且它是运行时的实现细节,需要随心所欲地改变.你在这个答案中看到了一些关于这些启发式的本质的好猜测:

  • 当T的任何静态方法被检测时,检查T的cctor是否需要运行
  • 当访问任何访问T的静态字段的方法被jitted时,检查T的cctor是否需要运行

这些启发式算法将满足宽松语义的要求,并且可以避免在呼叫站点发出所有检查,并且仍然可以确保合理的行为.

但你不能依赖那些猜测.所有你可以依赖的是,cctor将在第一次字段访问之前的某个时刻运行,这就是你得到的.在特定方法中是否存在字段访问显然是该启发式的一部分,但这些启发式方法可能会发生变化.

  • @wardies:静态类只是 C# 语言的虚构;从 CLR 的角度来看,没有这样的事情。从 CLR 的角度来看,静态类只是一个完全正常的*抽象最终类*,它只包含静态成员。Abstract 防止它被实例化,final 防止它被扩展。与任何其他普通类相比,静态类中的 cctor 没有特殊规则。 (4认同)
  • "但那些启发式方法可能会改变" - 的确如此:https://codeblog.jonskeet.uk/2010/01/26/type-in​​itialization-changes-in-net-4-0/ (3认同)