程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> [C#]BeforeFieldInit與類靜態構造函數

[C#]BeforeFieldInit與類靜態構造函數

編輯:C#入門知識

如下代碼:
 
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 延遲調用這一點來延遲對單例對象的構造(餓汗模式),從而帶來一丁點的優化,但是在絕大部分情況下這一丁點的優化作用並不大!

 
 

摘自 飄飄白雲

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved