我通过Jon Skeet的article阅读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
}
}
输出结果为:
Init x1
Starting Main
Echo 1
After echo
但是如果没有标记的行,那么在没有调用静态字段x1
的情况下,输出为:
Starting Main
Init x1
Echo 1
After echo
因此,使用beforefieldinit
标记的对象的调用会影响其类型初始值设定项的调用吗?或者这是否属于他提到的beforefieldinit
的奇怪效果?
因此,beforefieldinit可以使类型初始化程序的调用更加懒惰或更加热切。
答案 0 :(得分:20)
我不确定这里有什么问题;也许如果我解释一下抖动的概念,那就会回答这个问题。
带有显式静态构造函数的静态类具有" strict"关于cctor何时运行的语义:它在第一次使用该类型成员之前立即运行。所以,如果你有
if (whatever) x = Foo.Bar;
如果Foo
为false,则whatever
的cctor不会运行,因为我们还没有遇到成员的实际使用。
想想这对于jitted代码意味着什么。 如何为具有此要求的语言编写抖动?
对于静态方法调用,你可以在每个调用站点放一点前传来检查cctor是否已经运行。但这会使每个呼叫站点变得更大更慢。
您可以将前传放入静态方法本身。这会使呼叫站点保持较小,但每次呼叫仍然会稍微慢一点。
或者,你可能很聪明,并且在静态方法第一次进行抖动时将检查放入抖动。这样,您只需支付一次的费用,并且呼叫网站保持较小。 jit成本变得更大,但只有很小一部分; jitting已经很贵了。
请注意,这样做会排除任何导致方法在第一次调用之前进行jitted的优化,因为这样的优化现在会引入正确性问题。优化几乎总是涉及权衡!
但对于字段访问,没有方法可以进行jit。抖动必须在每次访问可能是第一个的字段前面放一点前传。因此,访问字段不仅慢,而且代码也会 big 。
您可能会认为为什么不将该字段变成属性并将前缀放在getter和setter的jitting上?,但这不起作用,因为字段是变量和属性不是。例如,我们需要能够通过ref
和out
传递静态字段,但您无法通过属性执行此操作。该字段可能是volatile
,也不能是属性。等等。
能够在现场访问中避免这些费用会很好。
没有显式cctor但使用编译器生成的隐式cctor初始化静态字段的静态类得到了放松"语义,其中抖动仅仅保证在访问字段之前在某个点调用cctor。您的程序使用这些轻松的语义。
在第一个版本中,通过字段访问,抖动从其对方法的分析中知道可以访问静态字段。 (为什么"可能"?和以前一样,访问权限可以在if
之下。)允许抖动在第一次访问之前的任何时间运行cctor,所以它的作用是说明当Main
被jitted时,检查Test1 cctor是否已经运行,如果没有,则运行它。
如果Main第二次被召唤,嘿,它只会被发送一次。因此,支票的成本仅在第一次通话时承担。 (当然Main在大多数程序中只被调用过一次,但是如果你进入那种东西,你可以编写一个递归的Main。)
在您的第二个程序中,没有字段访问权限。抖动也可能导致访问静态方法,并且可以在jit时间为Main运行cctor。它不是。为什么不?我不知道;你不得不向抖动小组询问这个问题。但关键是抖动完全在其权利中使用启发式来决定是否在jit时运行cctor,并且它是。
抖动也在其使用启发式方法来决定是否调用触摸无字段的静态方法触发cctor的权利;在这种情况下,显然它会不必要地这样做。
您的问题似乎是"这些启发式是什么?"答案是......好吧,我不知道答案是什么,而且它是运行时的实现细节,可能会随心所欲地改变。你在这个答案中看到了一些关于这些启发式的本质的好猜测:
这些启发式算法可以满足宽松语义的要求,并且可以避免在呼叫站点发出所有检查,并且仍然可以确保合理的行为。
但你不能依赖那些猜测。您可以依赖的是,在第一次访问字段之前,cctor将在某个点运行,这就是您所获得的内容。是否在特定方法中存在字段访问显然是该启发式的一部分,但这些启发式方法可能会发生变化。