[C#]BeforeFieldInit 與類靜態建構函式

來源:互聯網
上載者:User

BeforeFieldInit 與類靜態建構函式

羅朝輝 (http://kesalin.cnblogs.com/)

本文遵循“署名-非商業用途-保持一致”創作公用協議

如下代碼:

using System;namespace BeforeFieldInit{    internal class Foo    {        Foo(){ Console.WriteLine("Foo 物件建構函數");}        public static string Field = GetString("初始化 Foo 靜態成員變數!");        public static string GetString(string s){            Console.WriteLine(s);            return s;        }    }    internal class FooStatic    {        static FooStatic(){ Console.WriteLine("FooStatic 類建構函式"); }        FooStatic(){ Console.WriteLine("FooStatic 物件建構函數"); }        public static string Field = GetString("初始化 FooStatic 靜態成員變數!");        public static string GetString(string s){            Console.WriteLine(s);            return s;        }    }    class Program    {       static void Main(string[] args){            Console.WriteLine("Main 開始 ...");            Foo.GetString("手動調用 Foo.GetString() 方法!");            //string info = Foo.Field;            FooStatic.GetString("手動調用 FooStatic.GetString() 方法!");            //string infoStatic = FooStatic.Field;            Console.ReadLine();        }    }}

Foo 和 FooStatic 唯一的不同就是 FooStatic 有靜態類建構函式。執行上面的代碼,輸出如下:

如果把被注釋的讀取靜態欄位Field的兩行代碼開啟,再編譯運行,輸出:

對比上面的區別,FooStatic 始終是延遲裝載的,也就是只有類被首次使用時,類對象才被構造,其靜態成員以及靜態建構函式才被初始化執行, 而 Foo 類對象的初始化則交給 CLR 來決定。

如果用 IL Dasm.exe對比兩個類產生的中間代碼,可以看到只有一處不同:FooStatic 比 Foo 少了一個特性:beforefieldinit。

也就是說靜態建構函式抑制了 beforefieldinit 特性,而該特性會影響對調用該類的時機。

C# 裡面的靜態建構函式,也稱為類型構造器,類型初始化器,它是私人的,就是在中的 .cctor : void()。CLR保證一個靜態建構函式在每個AppDomain中只執行一次,而且這種執行是安全執行緒的,所以在靜態建構函式中非常適合於單例模式的初始化(初始化靜態欄位等同於在靜態建構函式中初始化,但不完全相同,因為顯式定義靜態建構函式會抑制beforefieldinit標誌。)。

JIT編譯器在編譯一個方法時,會查看代碼中引用了哪些類型,任何一個類型定義了靜態建構函式,JIT編譯器都會檢查針對當前 AppDomain,是否執行了這個靜態建構函式。如果類型構造去沒有執行,JIT編譯器就會在產生的本地代碼中添加對靜態建構函式的一個調用,否則就不會添加,因為類型已經初始化。同時CLR還保證在執行本地代碼中產生的靜態構造函代碼的安全執行緒。

根據上面的描述,我們知道 JIT 必須決定是否組建類型靜態建構函式代碼,還須決定何時調用它。具體在何時調用有兩中方式:

precise:JIT編譯器可以剛好在建立類型的第一個執行個體之前,或剛好在訪問類的一個非繼承的欄位或成員之前生產這個調用。

beforefieldinit:JIT編譯器可以在首次訪問一個靜態欄位或者一個靜態/執行個體方法之前,或者建立類型的第一個執行個體之前,隨便找一個時間產生調用。具體調用時機由CLR決定,它只保證訪問成員之前會執行靜態建構函式,但可能會提前很早就執行。

CLI specification (ECMA 335) 在 8.9.5 節中提到:

  1. 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
  2. If not marked BeforeFieldInit then that type's initializer method is executed at (i.e., is triggered by):
  • first access to any static or instance field of that type, or
  • first invocation of any static, instance or virtual method of that type

簡單點說就是beforefieldinit可能會提前調用一個類型的靜態建構函式,而precise模式是非要等到用時才調用類型的靜態建構函式,它是嚴格的延遲裝載。

beforefieldinit 是首選的(如果沒有自訂靜態建構函式,預設就是這種方式),因為它使CLR能夠自由選擇調用靜態建構函式的時機,而CLR會儘可能利用這一點來產生運行得更快的代碼。比如說在一個迴圈中調用單例(且包含首次調用),beforefieldinit方式可以讓CLR決定在迴圈之前就調用靜態建構函式來最佳化,而precise模式則只會在迴圈體中來調用靜態建構函式,並在之後的調用會檢測靜態建構函式是否已被執行的標誌位,這樣效率稍低一些。在前面使用靜態Field的情況下,beforefieldinit 方式下CLR也認為提前執行靜態建構函式是更好的選擇。

C# 的單例實現,可以利用 precise 延遲調用這一點來延遲對單例對象的構造(餓汗模式),從而帶來一丁點的最佳化,但是在絕大部分情況下這一丁點的最佳化作用並不大!

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.