1.BeforeFieldInit是什么
前段時間在反編譯代碼時無意間看到在類中有一個BeforeFieldInit特性,處於好奇的心態查了查這個特性,發現這是一個關於字段初始化時間的特性【提前初始化字段】,下面先來看一下這個特性在.net framework中的作用
class Foo { public static String x = GetStr("初始化 Foo 靜態成員字段"); public static String GetStr(String str) { Console.WriteLine(str); return str; } }
在上面Foo類中只定義了一個靜態字段x和一個靜態方法GetStr的方法,在這里需要關注的是靜態字段x的初始化時機
static void Main(string[] args) { Console.WriteLine("Main方法開始"); Foo.GetStr("手動調用Foo.GetSring()方法"); String y = Foo.x; }
在Main中簡單的調用靜態方法和靜態字段,我們知道靜態字段的賦值是在靜態構造函數中進行的,那么輸出順序應該是 “Main方法開始”,”初始化Foo靜態成員字段“,”手動調用Foo.GetString()方法“,但是真的是這樣嗎,答案是錯的

可以看到靜態成員字段的初始化是在最開始,那么為什么會這樣呢,我們將代碼反編譯IL后會發現在類中具有一個beforefieldinit特性,
.class private auto ansi beforefieldinit BeoreFieldInitTest2.Foo extends [mscorlib]System.Object { } // end of class BeoreFieldInitTest2.Foo
那么BeforeFieldInit是什么,我找到了一篇文章有對BeforeFieldInit的詳細講解,在這里也不過多介紹,
2.取消BeforeFieldInit加載
那么該怎么取消beforefieldinit特性呢,其實很簡單,只需要在類中加入一個靜態構造函數即可
class Foo { public static string x = GetStr("初始化 Foo 靜態成員字段");
//空的靜態構造函數
static Foo(){} public static String GetStr(String str) { Console.WriteLine(str); return str; } }
然后此時輸入就如我們所猜測那樣

並且反編譯可以看到IL代碼也取消了beforefieldinit特性
.class private auto ansi BeoreFieldInitTest2.Foo extends [mscorlib]System.Object { } // end of class BeoreFieldInitTest2.Foo
下面就該進入正題,來看看.NET Core中不一樣的BeforeFieldInit
3.BeforeFieldInit在.NET Core 中的差異
將最開始的代碼在.NET Core中跑一跑會發現跟.NET Framework不一樣的操作
class Program { static void Main(string[] args) { Console.WriteLine("Main方法開始"); Foo.GetStr("手動調用Foo.GetSring()方法"); String y = Foo.x; } } class Foo { public static string x = GetStr("初始化 Foo 靜態成員字段"); public static String GetStr(String str) { Console.WriteLine(str); return str; } }

可以看到在.NET Core並沒有像.NET Framework那樣進行提前加載,並且加載貌似還延遲了,反編譯代碼可以看到beforefieldinit特性還在Foo類上
.class private auto ansi beforefieldinit BeforeFieldInitTest.Foo extends [System.Runtime]System.Object { } // end of class BeforeFieldInitTest.Foo
那么在.NET Core加入靜態構造函數會怎么呢?懷着各種疑惑進行測試
class Program { static void Main(string[] args) { Console.WriteLine("Main方法開始"); Foo.GetStr("手動調用Foo.GetSring()方法"); String y = Foo.x; } } class Foo { public static string x = GetStr("初始化 Foo 靜態成員字段"); //空的靜態構造函數 static Foo() { } public static String GetStr(String str) { Console.WriteLine(str); return str; } }

可以看到.NET Core中加入靜態構造函數以后輸出跟.NET Framework一致,也就說可以猜測.NET Core運行時對beforefieldinit特性進行了優化,當然這也只是我的猜測
4.利用.NET Core中beforefieldinit實現的單例
在.NET Framework中我們都是使用Lazy<>類來創建延遲加載單例,但是我們可以看到在.NET Core中beforefieldinit是延遲加載的,所以我們直接可以使用此方法來創建延遲安全單例,
class Program { static void Main(string[] args) { Console.WriteLine("Main方法開始"); Foo.GetStr("手動調用Foo.GetSring()方法"); Console.WriteLine("我是分隔符"); Console.WriteLine("我是分隔符"); var foo= Foo.CreateInstance; } } class Foo { public static Foo CreateInstance { get; } = new Foo(); private Foo() { Console.WriteLine("創建了Foo實例"); } public static String GetStr(String str) { Console.WriteLine(str); return str; } }
運行結果可以看到創建實例被延遲了,

當然,這種創建單例也是有缺點的,當類中還有其它靜態字段或屬性時,並且在外部進行了調用,那么此時也會初始化此屬性
class Program { static void Main(string[] args) { Console.WriteLine("Main方法開始"); Foo.GetStr("手動調用Foo.GetSring()方法"); var y = Foo.x;//調用靜態字段/屬性 Console.WriteLine("我是分隔符"); Console.WriteLine("我是分隔符"); var foo= Foo.CreateInstance; } } class Foo { public static string x = GetStr("初始化 Foo 靜態成員字段"); //加入了靜態字段或屬性 //public static String X { get; set; } = GetStr("初始化 Foo 靜態成員字段"); public static Foo CreateInstance { get; } = new Foo(); private Foo() { Console.WriteLine("創建了Foo實例"); } public static String GetStr(String str) { Console.WriteLine(str); return str; } }

也就是說在.NET Core中beforfieldinit特性時當有一個靜態變量被使用時就初始化所有靜態變量
