如何查询一个网站的空间服务商,活泼风格的网站,精品课程网站建设毕业设计论文,建设网站预期效果怎么写1 引言今天Artech兄在《关于Type Initializer和 BeforeFieldInit的问题#xff0c;看看大家能否给出正确的解释》一文中让我们认识了一个关于类型构造器调用执行的有趣示例#xff0c;其中也相应提出了一些关于beforefieldinit对于类型构造器调用时机的探讨#xff0c;对于我…1 引言今天Artech兄在《关于Type Initializer和 BeforeFieldInit的问题看看大家能否给出正确的解释》一文中让我们认识了一个关于类型构造器调用执行的有趣示例其中也相应提出了一些关于beforefieldinit对于类型构造器调用时机的探讨对于我们很好的理解类型构造器给出了一个很好的应用实践体验。 作为补充本文希望从基础开始再层层深入把《关于Type Initializer和 BeforeFieldInit的问题看看大家能否给出正确的解释》一文中没有解释的概念和原理进行必要的补充例如更全面的认识类型构造器认识BeforeFieldInit。并在此基础上探讨一点关于类型构造器的实践应用同时期望能够回答其中示例运行的结果。 废话少说我们开始。
2 认识对象构造器和类型构造器
在.NET中一个类的初始化过程是在构造器中进行的。并且根据构造成员的类型分为类型构造器.cctor和对象构造器.ctor 其中.cctor和.ctor为二者在IL代码中的指令表示。.cctor不能被直接调用其调用规则正是本文欲加阐述的重点详见后文的分析而.ctor会在类型实例化时被自动调用。 基于对类型构造器的探讨我们有必要首先实现一个简单的类定义其中包括普通的构造器和静态构造器例如 span stylecolor:blackspan stylecolor:black span stylecolor:#008000// Release : code01, 2008/11/02 /span/span/span // Author : Anytao, http://www.anytao.com span stylecolor:blackspan stylecolor:black span stylecolor:#0000ffpublic/span span stylecolor:#0000ffclass/span User/span/span { span stylecolor:blackspan stylecolor:black span stylecolor:#0000ffstatic/span User()/span/span { span stylecolor:blackspan stylecolor:black message span stylecolor:#006080Initialize in static constructor./span;/span/span } span stylecolor:blackspan stylecolor:black /span/span public User() span stylecolor:blackspan stylecolor:black {/span/span message Initialize in normal construcotr.; span stylecolor:blackspan stylecolor:black }/span/span span stylecolor:blackspan stylecolor:black span stylecolor:#0000ffpublic/span User(span stylecolor:#0000ffstring/span name, span stylecolor:#0000ffint/span age)/span/span { span stylecolor:blackspan stylecolor:black Name name;/span/span Age age; span stylecolor:blackspan stylecolor:black }/span/span span stylecolor:blackspan stylecolor:black span stylecolor:#0000ffpublic/span span stylecolor:#0000ffstring/span Name { get; set; }/span/span span stylecolor:blackspan stylecolor:black span stylecolor:#0000ffpublic/span span stylecolor:#0000ffint/span Age { get; set; }/span/span span stylecolor:blackspan stylecolor:black span stylecolor:#0000ffpublic/span span stylecolor:#0000ffstatic/span span stylecolor:#0000ffstring/span message span stylecolor:#006080Initialize when defined./span;/span/span 我们将上述代码使用ILDasm.exe工具反编译为IL代码可以很方便的找到相应的类型构造器和对象构造器的影子如图 然后我们简单的来了解一下对象构造器和类型构造器的概念。
对象构造器.ctor
在生成的IL代码中将可以看到对应的ctor类型实例化时会执行对应的构造器进行类型初始化的操作。 关于实例化的过程设计到比较复杂的执行顺序按照类型基础层次进行初始化的过程可以参阅《你必须知道的.NET》7.8节 “动静之间静态和非静态”一文中有详细的介绍和分析本文中将不做过多探讨。 本文的重点以考察类型构造器为主所以在此不进行过多探讨。
类型构造器.cctor
用于执行对静态成员的初始化在.NET中类型在两种情况下会发生对.cctor的调用
为静态成员指定初始值例如上例中只有静态成员初始化而没有静态构造函数时.cctor的IL代码实现为span stylecolor:blackspan stylecolor:black.method span stylecolor:#0000ffprivate/span hidebysig specialname rtspecialname span stylecolor:#0000ffstatic/span /span/span void .cctor() cil managed span stylecolor:blackspan stylecolor:black{/span/span // Code size 11 (0xb) span stylecolor:blackspan stylecolor:black .maxstack 8/span/span IL_0000: ldstr Initialize when defined. span stylecolor:blackspan stylecolor:black IL_0005: stsfld span stylecolor:#0000ffstring/span Anytao.Write.TypeInit.User::message/span/span IL_000a: ret span stylecolor:blackspan stylecolor:black} span stylecolor:#008000// end of method User::.cctor/span/span/span 实现显式的静态构造函数例如上例中有静态构造函数存在时将首先执行静态成员的初始化过程再执行静态构造函数初始化过程.cctor的IL代码实现为span stylecolor:blackspan stylecolor:black.method span stylecolor:#0000ffprivate/span hidebysig specialname rtspecialname span stylecolor:#0000ffstatic/span /span/span void .cctor() cil managed span stylecolor:blackspan stylecolor:black{/span/span // Code size 23 (0x17) span stylecolor:blackspan stylecolor:black .maxstack 8/span/span IL_0000: ldstr Initialize when defined. span stylecolor:blackspan stylecolor:black IL_0005: stsfld span stylecolor:#0000ffstring/span Anytao.Write.TypeInit.User::message/span/span IL_000a: nop span stylecolor:blackspan stylecolor:black IL_000b: ldstr span stylecolor:#006080Initialize in static constructor./span/span/span IL_0010: stsfld string Anytao.Write.TypeInit.User::message span stylecolor:blackspan stylecolor:black IL_0015: nop/span/span IL_0016: ret span stylecolor:blackspan stylecolor:black} span stylecolor:#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 stylecolor:blackspan stylecolor:black span stylecolor:#008000// Release : code02, 2008/11/02 /span/span/span // Author : Anytao, http://www.anytao.com span stylecolor:blackspan stylecolor:black span stylecolor:#0000ffpublic/span span stylecolor:#0000ffclass/span User/span/span { span stylecolor:blackspan stylecolor:black span stylecolor:#008000//Explicit Constructor/span/span/span static User() span stylecolor:blackspan stylecolor:black {/span/span message Initialize in static constructor.; span stylecolor:blackspan stylecolor:black }/span/span span stylecolor:blackspan stylecolor:black span stylecolor:#0000ffpublic/span span stylecolor:#0000ffstatic/span span stylecolor:#0000ffstring/span message span stylecolor:#006080Initialize when defined./span;/span/span } 对应的IL代码为 span stylecolor:blackspan stylecolor:black.span stylecolor:#0000ffclass/span span stylecolor:#0000ffpublic/span auto ansi User/span/span extends [mscorlib]System.Object span stylecolor:blackspan stylecolor:black{/span/span .method private hidebysig specialname rtspecialname static void .cctor() cil managed span stylecolor:blackspan stylecolor:black {/span/span .maxstack 8 span stylecolor:blackspan stylecolor:black L_0000: ldstr span stylecolor:#006080Initialize when defined./span/span/span L_0005: stsfld string Anytao.Write.TypeInit.User::message span stylecolor:blackspan stylecolor:black L_000a: nop /span/span L_000b: ldstr Initialize in static constructor. span stylecolor:blackspan stylecolor:black L_0010: stsfld span stylecolor:#0000ffstring/span Anytao.Write.TypeInit.User::message/span/span L_0015: nop span stylecolor:blackspan stylecolor:black L_0016: ret /span/span } span stylecolor:blackspan stylecolor:black /span/span .method public hidebysig specialname rtspecialname instance void .ctor() cil managed span stylecolor:blackspan stylecolor:black {/span/span .maxstack 8 span stylecolor:blackspan stylecolor:black L_0000: ldarg.0 /span/span L_0001: call instance void [mscorlib]System.Object::.ctor() span stylecolor:blackspan stylecolor:black L_0006: ret /span/span } span stylecolor:blackspan stylecolor:black /span/span .field public static string message span stylecolor:blackspan stylecolor:black}/span/span 为了进行对比分析我们需要首先分析beforefieldinit方式的执行情况所以接着继续。。。 3.2 beforefieldinit方式 为User类型不实现显式的静态构造函数方案为 span stylecolor:blackspan stylecolor:black span stylecolor:#008000// Release : code03, 2008/11/02 /span/span/span // Author : Anytao, http://www.anytao.com span stylecolor:blackspan stylecolor:black span stylecolor:#0000ffpublic/span span stylecolor:#0000ffclass/span User/span/span { span stylecolor:blackspan stylecolor:black span stylecolor:#008000//Implicit Constructor/span/span/span public static string message Initialize when defined.; span stylecolor:blackspan stylecolor:black }/span/span 对应的IL代码为 span stylecolor:blackspan stylecolor:black.span stylecolor:#0000ffclass/span span stylecolor:#0000ffpublic/span auto ansi beforefieldinit User/span/span extends [mscorlib]System.Object span stylecolor:blackspan stylecolor:black{/span/span .method private hidebysig specialname rtspecialname static void .cctor() cil managed span stylecolor:blackspan stylecolor:black {/span/span .maxstack 8 span stylecolor:blackspan stylecolor:black L_0000: ldstr span stylecolor:#006080Initialize when defined./span/span/span L_0005: stsfld string Anytao.Write.TypeInit.User::message span stylecolor:blackspan stylecolor:black L_000a: ret /span/span } span stylecolor:blackspan stylecolor:black /span/span .method public hidebysig specialname rtspecialname instance void .ctor() cil managed span stylecolor:blackspan stylecolor:black {/span/span .maxstack 8 span stylecolor:blackspan stylecolor:black L_0000: ldarg.0 /span/span L_0001: call instance void [mscorlib]System.Object::.ctor() span stylecolor:blackspan stylecolor:black L_0006: ret /span/span } span stylecolor:blackspan stylecolor:black /span/span .field public static string message span stylecolor:blackspan stylecolor: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 stylecolor:blackspan stylecolor:blackspan stylecolor:#0000ffstring/span field Foo.Field;/span/span 执行的结果不受影响。
而在没有显式实现静态构造函数的情况下beforefieldinit优化了类型构造器的执行不在确定的时间执行只要实在静态成员引用或者类型实例发生之前即可所以在Debug环境下调用的时机变得不按常理。然而在Release优化模式下beforefieldinit的执行顺序并不受span stylecolor:blackspan stylecolor:blackspan stylecolor:#0000ffstring/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参考文献
《你必须知道的.NET》7.8节 “动静之间静态和非静态”Artech关于Type Initializer和 BeforeFieldInit的问题看看大家能否给出正确的解释通过七个关键编程技巧得益于静态内容 #53楼 2009-10-27 22:25 fisea 请教lz一个问题 如下程序代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Program { static void Main() { Console.WriteLine(Start ...); Foo.GetString(Manually invoke the static GetString() method!); } } class Foo { public static string Field GetString(Initialize the static field!); public static string GetString(string s) { Console.WriteLine(s); return s; } } 的运行结果如下 Start ... Initialize the static field! Manually invoke the static GetString() method! 和如下代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Program { static void Main() { Console.WriteLine(Main execute!); Console.WriteLine(int: MyClassint.Time); Thread.Sleep(3000); Console.WriteLine(string: MyClassstring.Time); Console.ReadLine(); } } public static class MyClassT { public static readonly DateTime Time GetNow(); private static DateTime GetNow() { Console.WriteLine(GetNow execute!); return DateTime.Now; } } 的运行结果 GetNow execute! GetNow execute! Main execute! int: 2009-10-27 22:20:06 string: 2009-10-27 22:20:06 问题 1、第一个例子为什么先执行Main中的Console.WriteLine(Start ...); 而第二个例子是先执行MyClass类中GetNow()函数。 2、第二个例子中的运行结果中为什么有两个GetNow execute! GetNow execute!。请教lz。谢谢。 支持(0) 反对(0) #54楼 2010-05-05 22:12 Edenia LZ能否回答一下53楼的问题我也感觉很迷茫O(∩_∩)O谢谢了~ 支持(0) 反对(0) #55楼 2010-11-29 21:05 李董 引用fisea请教lz一个问题 如下程序代码 [codecsharp] class Program { static void Main() { Console.WriteLine(Start ...); Foo.GetString(Manually invoke the static GetString() method!); } } class Foo { public ... 第一段代码中运行到Foo.GetString(Manually invoke the static GetString() method!);这句时会先执行该类的静态变量和静态构造函数此处未显示定义静态构造函数静态变量初始化后再执行上面的这句方法。 支持(0) 反对(0) #56楼 2011-02-16 16:14 王磊的博客 晚上睡觉不 哥不累啊自娱自乐的疯狂技术侠客佩服 支持(0) 反对(0) #57楼 2013-07-27 11:50 String.Trim() Edenia fisea 1.第二段代码中有 Console.WriteLine(int: MyClassint.Time); Console.WriteLine(string: MyClassstring.Time); 对Time静态成员进行引用 而MyClassT类中没有类型静态构造器所以CLR可以在任何时候执行类型构造器只要是在静态成员引用或者类型实例发生之前(不包括静态方法)即可。会在Main方法之前执行。 第一段代码没有对静态成员进行引用所以会按照顺序执行代码。 2.泛型类只有在具有相同类型形参的类型的实例才能够共享同一个静态字段的值。MyClassint与MyClassstring中的静态字段值是不同的所以GetNow()会执行2遍。