閱讀524 返回首頁    go 阿裏雲 go 技術社區[雲棲]


關於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()之前,需要完成靜態成員的初始化,所以最終的輸出結果如下圖所示:

image

然後我們在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 ...");之前執行)

image

“神奇”的事情還沒有結束,如果我們在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: }

再來看看現在執行結果,又和先前的一樣的了。

image 
我先不做任何評論(因為我也不太確定我的認識就是正確的),看看大家對此有什麼看法。

再添加另一個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的靜態構造函數。

image

個人覺得,這是編譯器值得改進的地方,既然靜態方法是基於類型的方法,隻能通過定義了該靜態方法的那個類型進行調用,至於其他的類,哪怕是該類的子類,都不能調用該方法。編譯器不應該讓這樣的代碼通過編譯。不知道讀者的意見如何。



作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-30 16:04:22

  上一篇:go  深度學習小技巧(二):如何保存和恢複scikit-learn訓練的模型
  下一篇:go  WCF中的Binding模型之二: 信道與信道棧(Channel and Channel Stack)