[你必须知道的.NET]第二十三回:品味细节,深入.NET的类型构造器

1 引言

今天Artech兄在《关于Type Initializer和 BeforeFieldInit的问题,看看大家能否给出正确的解释》一文中让我们认识了一个关于类型构造器调用执行的有趣示例,其中也相应提出了一些关于beforefieldinit对于类型构造器调用时机的探讨,对于我们很好的理解类型构造器给出了一个很好的应用实践体验。 
作为补充,本文希望从基础开始再层层深入,把《关于Type Initializer和 BeforeFieldInit的问题,看看大家能否给出正确的解释》一文中没有解释的概念和原理,进行必要的补充,例如更全面的认识类型构造器,认识BeforeFieldInit。并在此基础上,探讨一点关于类型构造器的实践应用,同时期望能够回答其中示例运行的结果。 
废话少说,我们开始。

2 认识对象构造器和类型构造器

在.NET中,一个类的初始化过程是在构造器中进行的。并且根据构造成员的类型,分为类型构造器(.cctor)和对象构造器(.ctor), 其中.cctor和.ctor为二者在IL代码中的指令表示。.cctor不能被直接调用,其调用规则正是本文欲加阐述的重点,详见后文的分析;而.ctor会在类型实例化时被自动调用。 
基于对类型构造器的探讨,我们有必要首先实现一个简单的类定义,其中包括普通的构造器和静态构造器,例如

<span style="color:black"><span style="color:black">    <span style="color:#008000">// Release : code01, 2008/11/02                </span></span></span>
    // Author  : Anytao, http://www.anytao.com 
<span style="color:black"><span style="color:black">    <span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> User</span></span>
    {
<span style="color:black"><span style="color:black">        <span style="color:#0000ff">static</span> User()</span></span>
        {
<span style="color:black"><span style="color:black">            message = <span style="color:#006080">"Initialize in static constructor."</span>;</span></span>
        }
<span style="color:black"><span style="color:black"> </span></span>
        public User()
<span style="color:black"><span style="color:black">        {</span></span>
            message = "Initialize in normal construcotr.";
<span style="color:black"><span style="color:black">        }</span></span>
 
<span style="color:black"><span style="color:black">        <span style="color:#0000ff">public</span> User(<span style="color:#0000ff">string</span> name, <span style="color:#0000ff">int</span> age)</span></span>
        {
<span style="color:black"><span style="color:black">            Name = name;</span></span>
            Age = age;
<span style="color:black"><span style="color:black">        }</span></span>
 
<span style="color:black"><span style="color:black">        <span style="color:#0000ff">public</span> <span style="color:#0000ff">string</span> Name { get; set; }</span></span>
 
<span style="color:black"><span style="color:black">        <span style="color:#0000ff">public</span> <span style="color:#0000ff">int</span> Age { get; set; }</span></span>
 
<span style="color:black"><span style="color:black">        <span style="color:#0000ff">public</span> <span style="color:#0000ff">static</span> <span style="color:#0000ff">string</span> message = <span style="color:#006080">"Initialize when defined."</span>;</span></span>

我们将上述代码使用ILDasm.exe工具反编译为IL代码,可以很方便的找到相应的类型构造器和对象构造器的影子,如图

然后,我们简单的来了解一下对象构造器和类型构造器的概念。

  • 对象构造器(.ctor)

在生成的IL代码中将可以看到对应的ctor,类型实例化时会执行对应的构造器进行类型初始化的操作。 
关于实例化的过程,设计到比较复杂的执行顺序,按照类型基础层次进行初始化的过程可以参阅《你必须知道的.NET》7.8节 “动静之间:静态和非静态”一文中有详细的介绍和分析,本文中将不做过多探讨。 
本文的重点以考察类型构造器为主,所以在此不进行过多探讨。

  • 类型构造器(.cctor)

用于执行对静态成员的初始化,在.NET中,类型在两种情况下会发生对.cctor的调用:

  • 为静态成员指定初始值,例如上例中只有静态成员初始化,而没有静态构造函数时,.cctor的IL代码实现为:
<span style="color:black"><span style="color:black">.method <span style="color:#0000ff">private</span> hidebysig specialname rtspecialname <span style="color:#0000ff">static</span> </span></span>
        void  .cctor() cil managed
<span style="color:black"><span style="color:black">{</span></span>
  // Code size       11 (0xb)
<span style="color:black"><span style="color:black">  .maxstack  8</span></span>
  IL_0000:  ldstr      "Initialize when defined."
<span style="color:black"><span style="color:black">  IL_0005:  stsfld     <span style="color:#0000ff">string</span> Anytao.Write.TypeInit.User::message</span></span>
  IL_000a:  ret
<span style="color:black"><span style="color:black">} <span style="color:#008000">// end of method User::.cctor</span></span></span>
  • 实现显式的静态构造函数,例如上例中有静态构造函数存在时,将首先执行静态成员的初始化过程,再执行静态构造函数初始化过程,.cctor的IL代码实现为:
<span style="color:black"><span style="color:black">.method <span style="color:#0000ff">private</span> hidebysig specialname rtspecialname <span style="color:#0000ff">static</span> </span></span>
        void  .cctor() cil managed
<span style="color:black"><span style="color:black">{</span></span>
  // Code size       23 (0x17)
<span style="color:black"><span style="color:black">  .maxstack  8</span></span>
  IL_0000:  ldstr      "Initialize when defined."
<span style="color:black"><span style="color:black">  IL_0005:  stsfld     <span style="color:#0000ff">string</span> Anytao.Write.TypeInit.User::message</span></span>
  IL_000a:  nop
<span style="color:black"><span style="color:black">  IL_000b:  ldstr      <span style="color:#006080">"Initialize in static constructor."</span></span></span>
  IL_0010:  stsfld     string Anytao.Write.TypeInit.User::message
<span style="color:black"><span style="color:black">  IL_0015:  nop</span></span>
  IL_0016:  ret
<span style="color:black"><span style="color:black">} <span style="color:#008000">// end of method User::.cctor</span></span></span>

同时,我们必须明确一些静态构造函数的基本规则,包括:

  • 必须为静态无参构造函数,并且一个类只能有一个。
  • 只能对静态成员进行初始化。
  • 静态无参构造函数可以和非静态无参构造函数共存,区别在于二者的执行时间,详见《你必须知道的.NET》7.8节 “动静之间:静态和非静态”的论述,其他更多的区别和差异也详见本节的描述。

3 深入执行过程

因为类型构造器本身的特点,在一定程度上决定了.cctor的调用时机并非是一个确定的概念。因为类型构造器都是private的,用户不能显式调用类型构造器。所以关于类型构造器的执行时机问题在.NET中主要包括两种方案:

  • precise方式
  • beforefieldinit方式

二者的执行差别主要体现在是否为类型实现了显式的静态构造函数,如果实现了显式的静态构造函数,则按照precise方式执行;如果没有实现显式的静态构造函数,则按照beforefieldinit方式执行。 
为了说清楚类型构造器的执行情况,我们首先在概念上必须明确一个前提,那就是precise的语义明确了.cctor的调用和调用存取静态成员的时机存在精确的关系,所以换句话说,类型构造器的执行时机在语义上决定于是否显式的声明了静态构造函数,以及存取静态成员的时机,这两个因素。 
我们还是从User类的实现说起,一一过招分析这两种方式的执行过程。 
3.1 precise方式 
首先实现显式的静态构造函数方案,为:

<span style="color:black"><span style="color:black">    <span style="color:#008000">// Release : code02, 2008/11/02                </span></span></span>
    // Author  : Anytao, http://www.anytao.com 
<span style="color:black"><span style="color:black">    <span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> User</span></span>
    {
<span style="color:black"><span style="color:black">        <span style="color:#008000">//Explicit Constructor</span></span></span>
        static User()
<span style="color:black"><span style="color:black">        {</span></span>
            message = "Initialize in static constructor.";
<span style="color:black"><span style="color:black">        }</span></span>
 
<span style="color:black"><span style="color:black">        <span style="color:#0000ff">public</span> <span style="color:#0000ff">static</span> <span style="color:#0000ff">string</span> message = <span style="color:#006080">"Initialize when defined."</span>;</span></span>
    }

对应的IL代码为:

<span style="color:black"><span style="color:black">.<span style="color:#0000ff">class</span> <span style="color:#0000ff">public</span> auto ansi User</span></span>
    extends [mscorlib]System.Object
<span style="color:black"><span style="color:black">{</span></span>
    .method private hidebysig specialname rtspecialname static void .cctor() cil managed
<span style="color:black"><span style="color:black">    {</span></span>
        .maxstack 8
<span style="color:black"><span style="color:black">        L_0000: ldstr <span style="color:#006080">"Initialize when defined."</span></span></span>
        L_0005: stsfld string Anytao.Write.TypeInit.User::message
<span style="color:black"><span style="color:black">        L_000a: nop </span></span>
        L_000b: ldstr "Initialize in static constructor."
<span style="color:black"><span style="color:black">        L_0010: stsfld <span style="color:#0000ff">string</span> Anytao.Write.TypeInit.User::message</span></span>
        L_0015: nop 
<span style="color:black"><span style="color:black">        L_0016: ret </span></span>
    }
<span style="color:black"><span style="color:black"> </span></span>
    .method public hidebysig specialname rtspecialname instance void .ctor() cil managed
<span style="color:black"><span style="color:black">    {</span></span>
        .maxstack 8
<span style="color:black"><span style="color:black">        L_0000: ldarg.0 </span></span>
        L_0001: call instance void [mscorlib]System.Object::.ctor()
<span style="color:black"><span style="color:black">        L_0006: ret </span></span>
    }
<span style="color:black"><span style="color:black"> </span></span>
    .field public static string message
<span style="color:black"><span style="color:black">}</span></span>

为了进行对比分析,我们需要首先分析beforefieldinit方式的执行情况,所以接着继续。。。 
3.2 beforefieldinit方式 
为User类型,不实现显式的静态构造函数方案,为:

<span style="color:black"><span style="color:black">    <span style="color:#008000">// Release : code03, 2008/11/02                </span></span></span>
    // Author  : Anytao, http://www.anytao.com 
<span style="color:black"><span style="color:black">    <span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> User</span></span>
    {
<span style="color:black"><span style="color:black">        <span style="color:#008000">//Implicit Constructor</span></span></span>
        public static string message = "Initialize when defined.";
<span style="color:black"><span style="color:black">    }</span></span>

对应的IL代码为:

<span style="color:black"><span style="color:black">.<span style="color:#0000ff">class</span> <span style="color:#0000ff">public</span> auto ansi beforefieldinit User</span></span>
    extends [mscorlib]System.Object
<span style="color:black"><span style="color:black">{</span></span>
    .method private hidebysig specialname rtspecialname static void .cctor() cil managed
<span style="color:black"><span style="color:black">    {</span></span>
        .maxstack 8
<span style="color:black"><span style="color:black">        L_0000: ldstr <span style="color:#006080">"Initialize when defined."</span></span></span>
        L_0005: stsfld string Anytao.Write.TypeInit.User::message
<span style="color:black"><span style="color:black">        L_000a: ret </span></span>
    }
<span style="color:black"><span style="color:black"> </span></span>
    .method public hidebysig specialname rtspecialname instance void .ctor() cil managed
<span style="color:black"><span style="color:black">    {</span></span>
        .maxstack 8
<span style="color:black"><span style="color:black">        L_0000: ldarg.0 </span></span>
        L_0001: call instance void [mscorlib]System.Object::.ctor()
<span style="color:black"><span style="color:black">        L_0006: ret </span></span>
    }
<span style="color:black"><span style="color:black"> </span></span>
    .field public static string message
<span style="color:black"><span style="color:black">}</span></span>

3.3 分析差别 
从IL代码的执行过程而言,我们首先可以了解的是在显式和隐式实现类型构造函数的内部,除了添加新的初始化操作之外,二者的实现是基本相同的。所以要找出两种方式的差别,我们最终将着眼点锁定在二者元数据的声明上,隐式方式多了一个称为beforefieldinit标记的指令。 
那么,beforefieldinit究竟表示什么样的语义呢?Scott Allen对此进行了详细的解释:beforefieldinit为CLR提供了在任何时候执行.cctor的授权,只要该方法在第一次访问类型的静态字段之前执行即可。 
所以,如果对precise方式和beforefieldinit方式进行比较时,二者的差别就在于是否在元数据声明时标记了beforefieldinit指令。precise方式下,CLR必须在第一次访问该类型的静态成员或者实例成员之前执行类型构造器,也就是说必须刚好在存取静态成员或者创建实例成员之前完成类型构造器的调用;beforefieldinit方式下,CLR可以在任何时候执行类型构造器,一定程度上实现了对执行性能的优化,因此较precise方式更加高效。 
值得注意的是,当有多个beforefieldinit构造器存在时,CLR无法保证这多个构造器之间的执行顺序,因此我们在实际的编码时应该尽量避免这种情况的发生。

4 回归问题,必要的小结

本文源于Artech兄的一个问题,希望通过上文的分析可以给出一点值得参考的背景。现在就关于Type Initializer和 BeforeFieldInit的问题,看看大家能否给出正确的解释一文中的几个示例进行一些继续的分析:

  • 在蒋兄的开始的示例实现中,可以很容易的来确定对于显式实现了静态构造函数的情况,类型构造器的调用在刚好引用静态成员之前发生,所以不管是否在Main中声明
<span style="color:black"><span style="color:black"><span style="color:#0000ff">string</span> field = Foo.Field;</span></span>

执行的结果不受影响。

  • 而在没有显式实现静态构造函数的情况下,beforefieldinit优化了类型构造器的执行不在确定的时间执行,只要实在静态成员引用或者类型实例发生之前即可,所以在Debug环境下调用的时机变得不按常理。然而在Release优化模式下,beforefieldinit的执行顺序并不受
<span style="color:black"><span style="color:black"><span style="color:#0000ff">string</span> field = Foo.Field;</span></span>

的影响,完全符合beforefieldinit优化执行的语义定义。

  • 关于最后一个静态成员继承情况的结果,正像本文开始描述的逻辑一样,类型构造器是在静态成员被调用或者创建实例时发生,所以示例的结果是完全遵守规范的。不过,我并不建议子类最好不要调用父类静态成员,原因是作为继承机制而言,子承父业是继承的基本规范,除了强制为private之外,所有的成员或者方法都应在子类中可见。而对于存在的潜在问题,更好的以规范来约束可能会更好。其中,静态方法一定程度上是一种结构化的实现机制,在面向对象的继承关系中,本质上就存在一定的不足。
  • 在c#规范中,关于beforefieldinit的控制已经引起很多的关注和非议,一方面beforefieldinit方式可以有效的优化调用性能,但是以显式和或者隐式实现静态构造函数的方式不能更有直观的让程序开发者来控制,因此在以后版本的c#中,能实现基于特性的声明方式来控制,是值得期待的。
  • 另一方面,在有两个类型的类型构造器相互引用的情况下,CLR无法保证类型构造器的调用顺序,对程序开发者而言,我同样强调了对于类型构造器而言,我们应该尽量避免要求顺序相关的业务逻辑,因为很多时候执行的顺序并非声明的顺序,这是值得关注的。

5 结论

除了补充Artech老兄的问题,本文算是继续了关于类型构造器在《你必须知道的.NET》7.8节 “动静之间:静态和非静态”中的探讨,以更全面的视角来进一步阐释这个问题。在最后,关于beforefieldinit标记引起的类型构造器调用优化的问题,虽然没有完全100%的了解在Debug模式下的CLR调用行为,但是深入细节我们可以掌控对于语言之内更多的理解,从这点而言,本文是个开始。

 

支持anytao的创业产品Worktile
Worktile,新一代简单好用、体验极致的团队协同、项目管理工具,让你和你的团队随时随地一起工作。完全免费,现在就去了解一下吧。
https://worktile.com

 

参考文献

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/438713.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[你必须知道的.NET]第二十四回:认识元数据和IL(上)

说在&#xff0c;开篇之前很早就有说说Metadata&#xff08;元数据&#xff09;和IL&#xff08;中间语言&#xff09;的想法了&#xff0c;一直在这篇开始才算脚踏实地的对这两个阶级兄弟投去些细关怀&#xff0c;虽然来得没有《第一回&#xff1a;恩怨情仇&#xff1a;is和as…

计算机无法找到组件c0000135,电脑显示没有找到dwmapi.dll组件怎么办?计算机丢失dwmapi.dll的处理方法...

很多用户在操作Windows系统的过程中发现“没有找到dwmapi.dll”&#xff0c;如果丢失dwmapi.dll组件会导致应用程序无法运行。其实&#xff0c;大家可以尝试在相关网站下载所缺少的组件&#xff0c;或者是通过第三方软件来进行安装下载&#xff0c;这里小编带领大家看看具体解决…

[你必须知道的.NET]第二十五回:认识元数据和IL(中)

说在&#xff0c;开篇之前书接上回[第二十四回&#xff1a;认识元数据和IL&#xff08;上&#xff09;]&#xff0c;我们对PE文件、程序集、托管模块&#xff0c;这些概念与元数据、IL的关系进行了必要的铺垫&#xff0c;同时顺便熟悉了以ILDASM工具进行反编译的基本方法认知&a…

小学计算机制作表格教案,小学信息技术《表格的制作》教案

小学信息技术《表格的制作》教案教学目标&#xff1a;知识目标&#xff1a;了解什么是网页表格能力目标&#xff1a;学会插入表格&#xff1b;掌握在表格中插入文字和图片的方法&#xff1b;学会设置单元格属性&#xff1b;掌握拆分和合并单元格。情感目标&#xff1a;通过研究…

[你必须知道的.NET]第二十六回:认识元数据和IL(下)

说在&#xff0c;开篇之前书接上回&#xff1a; 第二十四回&#xff1a;认识元数据和IL&#xff08;上&#xff09;&#xff0c; 第二十五回&#xff1a;认识元数据和IL&#xff08;中&#xff09; 我们继续。 终于到了&#xff0c;说说元数据和IL在JIT编译时的角色了&#x…

计算机电子电路原理图,学看电路原理图入门知识积累 - 全文

一、电子电路的意义电路图是人们为了研究和工程的需要&#xff0c;用约定的符号绘制的一种表示电路结构的图形。通过电路图可以知道实际电路的情况。这样&#xff0c;我们在分析电路时&#xff0c;就不必把实物翻来覆去地琢磨&#xff0c;而只要拿着一张图纸就可以了。在设计电…

[你必须知道的.NET]第二十八回:说说Name这回事儿

1 缘起 老赵在谈表达式树的缓存&#xff08;2&#xff09;&#xff1a;由表达式树生成字符串中提到&#xff0c;在描述Type信息时讨论FullName或者AssemblyQualifiedName提供完整的Type信息&#xff0c;虽是小话题&#xff0c;但却是值得有聊的话题。在.NET中反应一个Type名称…

library的英语怎么读音_【英语角】———学习方法分享

点击蓝字 关注我们每天学习一点点单词的记忆是一件很让人头疼的事情&#xff0c;但单词又是学习英语的基石&#xff0c;非常重要。那么有什么方法能让单词记忆变得简单有效呢&#xff1f;不妨试试下面这些方法吧。1、卡片记忆自制单词卡片&#xff0c;随身带着&#xff0c;有空…

计算机作文叙事,电脑争夺战叙事作文

电脑争夺战叙事作文在生活、工作和学习中&#xff0c;大家一定都接触过作文吧&#xff0c;作文是通过文字来表达一个主题意义的记叙方法。那么你有了解过作文吗&#xff1f;下面是小编帮大家整理的电脑争夺战叙事作文&#xff0c;欢迎阅读与收藏。这是一个温暖的下午&#xff0…

[你必须知道的.NET]第二十九回:.NET十年(上)

引言 语言是程序开发者行走江湖的手上利器&#xff0c;各大门派的高手在论坛、博客为了自家门派争吵不已早是技术世界中的亮丽风景&#xff0c;虽多少为刚刚踏入江湖的新手提供了思考的素材&#xff0c;但也同时迷惑了初出茅庐的前行方向。 本文不欲计较门派的高下&#xff0…

springboot 做表白墙_华广表白墙 第六期|hsl每天都想和你嘻嘻哈哈

1回复第五期 10 没了就没了&#xff0c;不值得就要留恋219级人力4班的银发女生看见你的第一眼就觉得你是一个天使&#xff0c;你的眼睛真的把我迷住了。如果可以的话能不能加你的微信&#xff0c;谢谢?3捞一下13号(周日晚上)21.15左右在校门口益禾堂买奶茶的小姐姐 金发 牛仔…

51系列计算机字长,计算机等级考试之MsOffice练习题第51套

为了让广大各位考生更好的复习&#xff0c;帮考网小编整理提供了2012计算机等级考试一级MsOffice精选题(51)&#xff0c;以供各位考生复习参考&#xff0c;希望对考生复习有所帮助。/计算机二级2012计算机等级考试一级MsOffice精选题(51)1)。 正确的IP地址是A) 202.112.111.1B)…

navcat定时备份mysql_Linux实现MYSQl数据库的定时备份

今天给大家分享一下如何在Linux下实现MYSQl数据库的定时备份。前提需要保证你的Linux服务器已经安装了MYSQl数据库服务。1、创建shell脚本vim backupdb.sh创建脚本内容如下&#xff1a;#!/bin/shdb_user"root"db_passwd"123456"db_name"userdb"n…

[你必须知道的.NET]第三十一回,深入.NET 4.0之,从“新”展望

总体来说&#xff0c;这是一篇介绍性的文章&#xff0c;不会涉及过多技术细节和研究过程。但是&#xff0c;作为拉开序幕的第一页&#xff0c;本文以提纲挈领的方式展开对.NET 4.0的初次体验。从What’s new的角度&#xff0c;开始我对.NET 4.0新特性的探索之旅。既然是介绍&am…

苹果计算机磁盘格式,Mac怎么将ntfs格式的磁盘格式化

1. 首先下载NTFS For Mac。下载之后就可以读取NTFS磁盘。2. 插入要格式化的NTFS磁盘。您可以在Mac OS X下通过命令行格式化NTFS磁盘。按照以下步骤进行操作&#xff1a;启动命令行&#xff1a;应用程序 》 工具 》 终端; 输入diskutil获取帮助。格式化命令图&#xff1a;使用di…

[你必须知道的.NET]第三十二回,,深入.NET 4.0之,Tuple一二

Tuple&#xff0c;是函数式编程的概念之一&#xff0c;早见于Elang、F#等动态语言。不过&#xff0c;我第一次听说Tuple还早在2005年园子的Ninputer大牛提出在.NET 2.0实现Tuple的基本想法&#xff0c;我们可以通过以下地址仰慕当时的历史片段&#xff1a; 探讨.NET 2.0中Tuple…

支持商用吗_可商用的插画素材 | 美翻了

好素材在手&#xff0c;天下我有啊...哈哈哈喽大家周末好&#xff0c;那上周公子做的那份工作型插画模板呢&#xff0c;很多小伙伴都来问我素材是哪里找的&#xff0c;自己画的吗当然不是了&#xff01;其实我在文章中已经提到了&#xff0c;那有的人可能之前用过或可以自己找到…

永恒边境白羊座服务器维护,永恒边境升级攻略 速升50级技巧

永恒边境怎么升级快&#xff1f;分享永恒边境升级攻略&#xff0c;下面我们就一起来看看永恒边境速刷主线支线任务技巧&#xff0c;希望对大家有所帮助。20-30级篇在这个阶段&#xff0c;我们就主线任务和支线任务大家都不要错过了&#xff0c;都要好好抓住&#xff0c;我还可以…

格式化css文件,css文件格式化脚本的方法

这次给大家带来css文件格式化脚本的方法&#xff0c;css文件格式化脚本的注意事项有哪些&#xff0c;下面就是实战案例&#xff0c;一起来看一下。#!/usr/bin/python# -*- coding: UTF-8 -*-import sys,osdef format(ddt):ddt ddt.replace(\n,)#去除换行ddtddt.replace(;},}).…

[你必须知道的.NET]第三十四回,object成员,不见了!

在.NET世界了&#xff0c;object是公认的造物主&#xff0c;其麾下的7大成员&#xff0c;个顶个的横行在任何系统的任何代码角落。 public class Object {public Object();public virtual bool Equals(object obj);public static bool Equals(object objA, object objB);publi…