关于Type Initializer和 BeforeFieldInit的问题,看看大家能否给出正确的解释

下面通过一个简单的Console Application演示Type Innitializer的执行顺序。希望大家各抒己见,对于实验的结果给出一个圆满的解释,同时希望读者从中理解到更多关于编译、关于CLR一些被我们忽略的细节。

代码如下,在类Foo中定义了两个static成员:静态字段Field和静态方法GetString,Field通过于Inline的方式通过调用GetString进行初始化。在Main()中仅仅两行代码:Console.WriteLine("Start
...");Foo.GetString("Manually invoke the static GetString() method!"); 

   1: using System;
   2: namespace Artech.TypeInitializerDemo
   3: {
   4:     class Program
   5:     {
   6:         static void Main()
   7:         {
   8:             Console.WriteLine("Start ...");
   9:             Foo.GetString("Manually invoke the static GetString() method!");
  10:         }
  11:     }
  12:  
  13:     class Foo
  14:     {
  15:         public static string Field = GetString("Initialize the static field!");
  16:  
  17:         public static string GetString(string s)
  18:         {
  19:             Console.WriteLine(s);
  20:             return s;
  21:         }
  22:     }
  23: } 

对于结果,我想很多人都能够猜得到,如果在显示调用GetString()之前,需要完成静态成员的初始化,所以最终的输出结果如下图所示:

然后我们在Main()种多加一行代码:string field = Foo.Field; 也就是获取Foo的静态字段:

   1: static void Main()
   2: {
   3:     Console.WriteLine("Start ...");
   4:     Foo.GetString("Manually invoke the static GetString() method!");
   5:     string field = Foo.Field;            
   6: }

最终的输出结果就和上面不一样了,静态字段的初始化工作居然提前了(在Console.WriteLine("Start ...");之前执行)

“神奇”的事情还没有结束,如果我们在Foo中加上一个静态构造函数,其中不执行任何的操作:

   1: class Foo
   2: {
   3:     public static string Field = GetString("Initialize the static field!");
   4:  
   5:     static Foo()
   6:     { }
   7:  
   8:     public static string GetString(string s)
   9:     {
  10:         Console.WriteLine(s);
  11:         return s;
  12:     }
  13: }

再来看看现在执行结果,又和先前的一样的了。

 
我先不做任何评论(因为我也不太确定我的认识就是正确的),看看大家对此有什么看法。

再添加另一个static
constructor的例子,较之上面一个要简单点。在Bar继承自基类Foo,在Foo和Bar均定义了静态构造函数。静态方法DoSomething()定义在Foo中,在Main()中却通过Bar.DoSomething();进行调用。

   1: using System;
   2: namespace Artech.TypeInitializerDemo
   3: {
   4:     class Program
   5:     {
   6:         static void Main()
   7:         {
   8:             Bar.DoSomething();
   9:         }
  10:     }
  11:  
  12:     public abstract class Foo
  13:     {
  14:         static Foo()
  15:         {
  16:             Console.WriteLine("static Foo() is invoked");
  17:         }
  18:  
  19:         public static void DoSomething()
  20:         {
  21:             Console.WriteLine("Done ...");
  22:         }
  23:     }
  24:  
  25:     public class Bar : Foo
  26:     {
  27:         static Bar()
  28:         {
  29:             Console.WriteLine("static Bar() is invoked");
  30:         }
  31:     }
  32: } 

下面是输出结果,可见虽然通过Bar调用了静态方法DoSomething,但是Bar的静态构造函数没有被执行。这个很好理解,因为Something是定义在基类Foo上,Bar.DoSomething()本质上相当于Foo.DoSomething()。所以只会调用Foo的静态构造函数。

个人觉得,这是编译器值得改进的地方,既然静态方法是基于类型的方法,只能通过定义了该静态方法的那个类型进行调用,至于其他的类,哪怕是该类的子类,都不能调用该方法。编译器不应该让这样的代码通过编译。不知道读者的意见如何。


作者:蒋金楠
微信公众账号:大内老A
微博:www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

原文链接

时间: 2024-10-30 05:26:00

关于Type Initializer和 BeforeFieldInit的问题,看看大家能否给出正确的解释的相关文章

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

1 引言 今天Artech兄在<关于Type Initializer和 BeforeFieldInit的问题,看看大家能否给出正确的解释>一文中让我们认识了一个关于类型构造器调用执行的有趣示例,其中也相应提出了一些关于beforefieldinit对于类型构造器调用时机的探讨,对于我们很好的理解类型构造器给出了一个很好的应用实践体验. 作为补充,本文希望从基础开始再层层深入,把<关于Type Initializer和 BeforeFieldInit的问题,看看大家能否给出正确的解释>

c#单例模式(Singleton)的6种实现_C#教程

1.1.1 摘要  在我们日常的工作中经常需要在应用程序中保持一个唯一的实例,如:IO处理,数据库操作等,由于这些对象都要占用重要的系统资源,所以我们必须限制这些实例的创建或始终使用一个公用的实例,这就是我们今天要介绍的--单例模式(Singleton).  使用频率高 单件模式(Singleton):保证一个类仅有一个实例,并提供一个访问它的全局访问点. 1.1.2 正文 图1单例模式(Singleton)结构图 单例模式(Singleton)是几个创建模式中最对立的一个,它的主要特点不是根据

当VS.NET2003遇上VS.NET2005,WebService部署何去何从

web 问题描叙] 重装操作系统之后,部署原来的Web Service出现无法访问.无法调试的问题 [原编程环境] VS.NET2003(.NET Framework 1.1 SP1),Windows XP SP1 [新编程环境] VS.NET2003(.NET Framework 1.1 SP1),VS.NET 2005 Beta1(.NET Framework 2.0 ),Windows XP SP2 [问题解决过程] 因为原来的项目一直使用工具VS.NET 2003,所以虽然VS.NET

.net-MAC下Unity C# 在线等

问题描述 MAC下Unity C# 在线等 别人写个unity的C#脚本,在PC上是可以跑得,但到MAC上走到Bitmap bitmap = (Bitmap)Bitmap.FromFile(path, false);;这里就报错:DllNotFoundException: libgdiplus.dylib System.Drawing.GDIPlus..cctor () Rethrow as TypeInitializationException: An exception was thrown

解决Crystal Report XI R2不能在64操作系统正常工作的问题-web程序

原文:[原创]解决Crystal Report XI R2不能在64操作系统正常工作的问题-web程序   我更换了新的电脑,操作系统也从原来32位的windows 2003 R2升级到windows 2008 R2 x64, 由于客户的原因我们的报表部分必须用Crystal Report XI R2来完成,报表在原来的计算机上运行一直没有问题,但是在新的计算机上一直不能正工作,错误如下: The type initializer for 'CrystalDecisions.CrystalRep

代码-c# 访问Exchange初始化时报错

问题描述 c# 访问Exchange初始化时报错 RunspaceConfiguration runspaceConfig = RunspaceConfiguration.Create(); PSSnapInException snapEX = null; PSSnapInInfo snampInfo = runspaceConfig.AddPSSnapIn("Microsoft.Exchange.Management.PowerShell.Admin", out snapEX); R

ubuntu 13.10编译安装mono环境(二)_Linux

准备工作 先在VS2012上编译一个winform,代码如下: using System; using System.Windows.Forms; namespace FormsTest { static class Program { /// <summary> /// 应用程序的主入口点. /// </summary> [STAThread] static void Main() { Application.EnableVisualStyles(); Application.S

基于Turbo C(V2.0)编译错误信息的详细介绍_C 语言

说明:Turbo C 的源程序错误分为三种类型:致命错误.一般错误和警告.其中,致命错误通常是内部编译出错:一般错误指程序的语法错误.磁盘或内存存取错误或命令行错误等:警告则只是指出一些得怀疑的情况,它并不防止编译的进行. 下面按字母顺序A-Z分别列出致命错误及一般错误信息,英汉对照及处理方法: (一).致命错误英汉对照及处理方法: A-B致命错误 Bad call of in-line function (内部函数非法调用)分析与处理:在使用一个宏定义的内部函数时,没能正确调用.一个内部函数以

Git.Framework 框架随手记--IIS7运行序列化问题

客户反馈系统又登录不了,这是最近几次连续出现相同的问题,从日志反应情况来看: 日志级别:[info] 日志位置:Git.Framework.Resource.ResourceManager 日志时间:2014/12/3 9:08:30 日志内容:反序列化异常:Unable to generate a temporary class (result=1). error CS1567: Error generating Win32 resource: 另一个程序正在使用此文件,进程无法访问. 日志级