C#中為什麼會出現空靜態構造方法的寫法

CodeBear發表於2019-02-02

再過幾個小時,就要回家過春節了,今天說些簡單點的東西,大家在看C#程式碼的時候,一定會對這樣的寫法非常迷茫:在一個類中會出現一個空的靜態構造方法。這不是多此一舉嗎,這樣做的目的是什麼?今天我就來說說這個內容。

前段時間,小夥伴遇到一個問題,百思不得其解,我先來模擬下這個問題:

 class Program
    {
        static void Main(string[] args)
        {
            //1.初始化配置中心
            Console.WriteLine("初始化配置中心");
           
            //2.利用從配置中心讀取出來的內容進行了一些操作
             String config= MyTest.config;
            //dosomething
        }
    }
    public class MyTest
    {
        public static string config = getConfig();

        private static string getConfig()
        {
            //讀取配置中心的內容,並返回
            return "";
        }
    }

程式碼比較簡單,就是有兩個類,一個是主程式入口,一個是業務類,在業務類裡面,定義了一個static的變數,給它賦上一個方法,方法中讀取了配置中心的內容,並且返回,那麼這個static的變數的值就是配置中心的內容了,在主程式入口,一開始就初始化了配置中心,然後訪問在業務類中的靜態變數,並且利用這個值,做一些後續操作。

我們先不管這樣的邏輯是否合理,就只看能否正常執行。

這樣的程式碼看上去並沒有什麼問題,但是讓人不解的是,丟擲了異常,內容是“配置中心未初始化”,小夥伴懵了,明明一開始就初始了配置中心啊,為什麼讀取配置中心內容的時候,還會出現這樣的異常呢。

我一看,立刻懂了,於是我在業務類中,加了一個靜態的構造方法,如下所示:

    public class MyTest
    {
        public static string config = getConfig();

        private static string getConfig()
        {
            //讀取配置中心的內容,並返回
            return "";
        }

        static MyTest() { }
    }

一切都好了。

我加了一個空的靜態方法,注意是空的,為什麼加了一個空的靜態方法可以解決問題呢?我們再來做個試驗把:

    class Program
    {
        static void Main(string[] args)
        {
            //初始化配置中心
            Console.WriteLine("初始化配置中心");

            //2.利用從配置中心讀取出來的內容進行了一些操作
            String config = MyTest.config;
            Console.WriteLine(config);
        }
    }
  public class MyTest
    {
        public static string config = getConfig();

        private static string getConfig()
        {
            Console.WriteLine("進到了getConfig方法");
            //讀取配置中心的內容,並返回
            return "配置中心的內容";
        }
    }

讓我們想想會輸出什麼?這還不簡單,當然是 初始化配置中心 進到了getConfig方法 配置中心的內容,但是,當我們執行:
image.png
你會發現,奇怪的事情出現了,第一個輸出的竟然是 進到了“getConfig方法”。

我們為MyTest類加上一個空的靜態構造方法,再看看:

 public class MyTest
    {
        public static string config = getConfig();

        private static string getConfig()
        {
            Console.WriteLine("進到了getConfig方法");
            //讀取配置中心的內容,並返回
            return "配置中心的內容";
        }

        static MyTest() { }
    }

image.png

輸出竟然被改變了。

這就是解釋了為什麼小夥伴一開始的程式碼會出現問題的原因,因為程式一上來,還沒有執行 初始化配置中心呢,直接讀取了配置中心的內容,而我加上的空靜態構造方法,就改變了程式碼的執行順序,是不是很神奇。

我們在用ILSpy看下IL程式碼,當類中沒有靜態構造方法的時候:
image.png
IL程式碼有一個標記:beforefieldinit

當類中的靜態構造方法的時候:
image.png
beforefieldinit標記消失了。

我們來做一個總結,當一個類中沒有靜態構造方法的時候,IL會有beforefieldinit標記,程式一執行,就會初始化靜態欄位,當一個類中有靜態構造方法的時候,IL沒有beforefieldinit標記,程式一開始就不會初始化靜態欄位,而是用到這個類了,才初始化靜態欄位。

現在我們可以解釋為什麼在餓漢式的單例模式中,經常會看到空的靜態構造方法了,因為不想讓程式在一開始的時候就初始化這個單例物件,而是用到了才去初始化,相當於懶載入,其實這也是一種優化,如果程式執行後,長時間沒有使用到這個單例物件,而一開始程式就把單例物件載入到記憶體中去了,也是一種浪費。

這篇的內容到這裡就結束了,哈哈,馬上就解放啦。

相關文章