zoukankan      html  css  js  c++  java
  • BeforeFieldInit解析(zz)

    废话不 说,先看一段代码:

          

    1. using System;  
    2. using System.Collections.Generic;  
    3. using System.Linq;  
    4. using System.Text;  
    5.   
    6. namespace BeforeIniField  
    7. {  
    8.     class Program  
    9.     {  
    10.         class TestStatic  
    11.         {  
    12.             public static string instance = GetString("Initialize the static field");  
    13.   
    14.             public static string GetString(string s)  
    15.             {  
    16.                 Console.WriteLine(s);  
    17.                 return s;  
    18.             }  
    19.   
    20.             static TestStatic() { }//注意这里是静态构造函数  
    21.         }  
    22.         static void Main(string[] args)  
    23.         {  
    24.             Console.WriteLine("Start main");  
    25.             TestStatic.GetString ("Manually invoke the static GetString () method!");  
    26.         }  
    27.     }  
    28. }  

          

    第一个问题,请预测该程序 的运行结果,恩,应该是打印出三段字符串,顺序是Start main,Initialize the static……,Manually invoke……回答正确。

    第二个问题,将static TestStatic() { }改为 TestStatic(),运行结果有无变化?

    答案是debug状态下无变化,请看下面的运行截图:

          

           但是Release状态下有变化:

          

          

          

    然后,我们给这个程序加点 料:修改Main函数中的代码如下:

            Console.WriteLine ("Start main");

            TestStatic.GetString ("Manually invoke the static GetString () method!");

     String instance = TestStatic. Instance; //这一行是增加的代码

     修改之后,在debug模式和Release模式下程序运行结果一致,如下:

    问题来了,为什么会这样 呢?注意到,Initialize the static field在Start Main之前打印出来,说明CLR提前调用 了TestStatic类的构造函数(在构造函数中会逐一初始化成员变量,包括静态成员变量,从而 打印出Initialize the static……)(后面你会发现,这里这句话并不精 确!)。而在文首的程序中,TestStatic类的静态构造函数仅仅在首次访问TestStatic的静态成员变量之前被初始化,为什么会存在这样的区别?

    为了更邪门的说明这个问 题,再奉上一段寂寞的代码:

    1. using System;  
    2. using System.Collections.Generic;  
    3. using System.Linq;  
    4. using System.Text;  
    5. using System.Diagnostics;  
    6.   
    7. namespace BeforeIniField  
    8. {  
    9.     class TestStatic  
    10.     {  
    11.         public static string instance = "Hello world";  
    12.   
    13.         public static string GetString(string s)  
    14.         {  
    15.             Console.WriteLine(s);  
    16.             return s;  
    17.         }  
    18.   
    19.         static TestStatic()  
    20.         {  
    21.         }  
    22.   
    23.     }  
    24.   
    25.     class Test  
    26.     {  
    27.         public static string instance = "Hello world";  
    28.   
    29.         public static string GetString(string s)  
    30.         {  
    31.             Console.WriteLine(s);  
    32.             return s;  
    33.         }  
    34.   
    35.         Test()  
    36.         {  
    37.         }  
    38.   
    39.     }  
    40.     class Program  
    41.     {  
    42.         private static void  TimeStatic()  
    43.         {  
    44.             Stopwatch sw = Stopwatch.StartNew();  
    45.         
    46.             for (int i = 0; i < Int32.MaxValue - 1; ++i)  
    47.             {  
    48.                 string msg = TestStatic.instance;  
    49.             }  
    50.             sw.Stop();  
    51.             Console.WriteLine(sw.ElapsedMilliseconds);  
    52.         }  
    53.   
    54.         private static void Time()  
    55.         {  
    56.             Stopwatch sw = Stopwatch.StartNew();  
    57.           
    58.             for (int i = 0; i < Int32.MaxValue - 1; ++i)  
    59.             {  
    60.                 string msg = Test.instance;  
    61.             }  
    62.             sw.Stop();  
    63.             Console.WriteLine(sw.ElapsedMilliseconds);  
    64.         }  
    65.   
    66.         static void Main(string[] args)  
    67.         {  
    68.             TimeStatic();  
    69.             Time();  
    70.         }  
    71.     }  

    注意到,没有声明静态构造 函数的时候,执行时间只相当于声明静态构造函数的一半。现在是不是感觉越来越糊涂了,没关系,下面会逐一进行解释。

    首先,拿出.NET下的终极武器,IL Dissembler, 微 软提供的一个反汇编程序。如下图:

    请注意beforeFieldInit,蓝色标记的部分,大家注意,C#编 译器为Test类也声明了一个静态构造函数,唯一的区别就是这个beforeFieldInit标记,这究竟是个什么玩意?

    首先,我们需要了解在.NET 中,一个类的初始化过程是在构造器中进行的。并且根据构造成员的类型,分为类型构造器(.cctor )和对象构造器(.ctor 两者的区别在于器(.ctor )在类型实例化时会执行对应的构造器进行类型初始化的操作,而.cctor用于执行对静态成员的初始化。.cctor 不能被直接调用,实际上其也是private的因为类型构造器都是private 的, 用户不能显式调用类型构造器。所以关于类型构造器的执行时机问题在.NET 中主要包括两种方案:

           beforeFieldInit方 式

           precise方式

    具体采 用哪种方案取决于是否为类型实现了显式的静态构造函数,如果实现了显式的静态构造函数,则按照precise 方 式执行;如果没有实现显式的静态构造函数,则按照beforefieldinit 方式执行。
        下面是“The CLI specification (ECMA 335) states in section 8.9.5:” 中关于beforeFieldInit和Precise两 种方式类型构造器调用时机的解释的原文:

    1.    A type may have a type-initializer method, or not. (小木注释:显然,Test类和TestStatic类都有类型构造器)

    2.    A type may be specified as having a relaxed semantic for its type-initializer method (for convenience below, we call this relaxed semantic BeforeFieldInit)

    3.    If marked BeforeFieldInit then the type's initializer method is executed at, or sometime before, first access to any static field defined for that type (小木注释:标注了BeforeFieldInit之后,类 型构造器的调用时机由CLR权衡)

    4.    If not marked BeforeFieldInit then that type's initializer method is executed at (i.e., is triggered by):

    o    first access to any static or instance field of that type, or

    o    first invocation of any static, instance or virtual method of that type

    从CLR的Specification中,我们可以对BeforeFieldInit的 作用做一个小结,BeforeFildInit方式中CLR拥 有对类型构造器的执行时间进行动态的调度的权限,beforefieldinit 为CLR 提供了在任何时候执行.cctor 的授权,只 要该方法在第一次访问类型的静态字段之前执行即可,强调性能。而Precise方式则是一种延迟调 用机制,打个比方,BeforeFieldInit可能会提前调用一个类型的类型构造器,而Precise模式是非要等到用时才调用类型构造器。

    好,再回到那个时间比较的程序上,为什么声明了静态构造函数之后,执行 效率便低呢?我从一篇博文上找到了如下的蛛丝马迹:

    In many cases, you won't actually require full laziness - unless your class initialization does something particularly time-consuming, or has some side-effect elsewhere, it's probably fine to leave out the explicit static constructor shown above. This can increase performance as it allows the JIT compiler to make a single check (for instance at the start of a method) to ensure that the type has been initialized, and then assume it from then on.
          这篇博文的意思是说,如果不使用显示静态构造函数(即以BeforeFieldInit) 方式,JIT编译器只需要做一次检查类型是否已经被初始化。以我的理解,反过来就是说,如果声明了 显示构造函数(以Precise方式),那么JIT每 一次都要检查类型的构造函数已被初始化,显然BeforeFieldInit方 式的效率较高!

    全文完.

    参考文献:王涛《你必须知道的.NET》

    CSDN artech专 栏 http://blog.csdn.net/artech

    百度,google.com inEnglish

    后记:写这篇文章是因为在看单例模式的时候碰巧遇到了这个问题,感觉很有意思,就在网上收罗了一些资 料,写成了这个

  • 相关阅读:
    行为型模式之Template Method模式
    【翻译】无需安装Python,就可以在.NET里调用Python库
    SciSharpCube:容器中的SciSharp,.NET机器学习开箱即用
    [翻译] NumSharp的数组切片功能 [:]
    C#
    API做翻页的两种思路
    C#相等性
    C#相等性
    SpringBoot进阶教程(五十八)整合Redis之Sentinel哨兵模式
    org.springframework.cache.interceptor.SimpleKey cannot be cast to java.lang.String
  • 原文地址:https://www.cnblogs.com/end/p/1763634.html
Copyright © 2011-2022 走看看