C# 9.0新特性詳解系列之三:模組初始化器

碼客風雲發表於2020-11-29

1 背景動機

關於模組或者程式集初始化工作一直是C#的一個痛點,微軟內部外部都有大量的報告反應很多客戶一直被這個問題困擾,這還不算沒有統計上的客戶。那麼解決這個問題,還有基於什麼樣的考慮呢?

  • 在庫載入的時候,能以最小的開銷、無需使用者顯式呼叫任何介面,使客戶做一些期望的和一次性的初始化。

  • 當前靜態建構函式方法的一個最大的問題是執行時會對帶有靜態建構函式的型別做一些額外的檢查。這是因為要決定靜態建構函式是否需要被執行所必須的一步,但是這個又有著顯著的開銷影響。

  • 使原始碼生成器在不需要使用者顯式呼叫一些東西的情況下能執行一些全域性的初始化邏輯。

2 詳細設計

C# 9.0將模組初始化器設計為一個Attribute,用這個Attribute來修飾進行模組初始化邏輯的方法,就實現了模組初始化功能。這個Attribute被命名為ModuleInitializerAttribute,具體定義如下:

using System;
namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
    public sealed class ModuleInitializerAttribute : Attribute { }
}

如果要使用模組初始化器,你只要將ModuleInitializerAttribute用在符合下面要求的方法上就可以了。

  1. 該方法必須使靜態的、無參的、返回值為void的函式。

  2. 該方法不能是泛型或者包含在泛型型別裡

  3. 該方法必須是可從其所在模組裡訪問的。也就是說,方法的有效訪問符必須是internal或者public,不能是區域性方法。

using System.Runtime.CompilerServices;
class MyClass
{
    [ModuleInitializer]
    internal static void Initializer()
    {
        // ...
    }
}

被修飾為ModuleInitializerAttribute的靜態方法會被編譯器在編譯時,在全域性的靜態建構函式中生成此程式碼呼叫。如果有多個被修飾為初始化器的函式,則每個函式生成一個初始化器程式碼呼叫,這些初始化器程式碼呼叫程式碼會按照一定的順序(型別名稱順序和程式碼順序)生成。當模組在被載入時,全域性靜態建構函式開始執行,從而完成模組程式碼初始化工作。

3 問題與最佳實踐

模組初始化器與靜態建構函式之間有著一定的關聯影響。因為模組初始化器是一個靜態方法,因而其被呼叫執行前,必然會引起其所處型別的靜態建構函式的執行。請參考下列示例:

static class ModuleInit
{
    static ModuleInit()
    {
        //先執行
        Console.WriteLine("ModuleInit靜態建構函式 cctor");
    }

    [ModuleInitializer]
    internal static void Initializer()
    {
        //在靜態建構函式執行後才執行
        Console.WriteLine("模組初始化器");
    }
}

在一個模組中指定多個模組初始化器的時候,他們之間的順序也是一個值得注意的問題。以上這些問題的存在,就要求我們注意以下幾點:

  • 在指定了模組初始化器的型別中,不要在靜態建構函式中,寫與模組初始化器中程式碼有著順序依賴程式碼,最好的就是不要使用靜態建構函式。

  • 多個模組初始化器之間的程式碼,也不要有任何依賴關係,保持各個初始化器程式碼的獨立性。

4 結束語]

日常開發中,我們通常需要在模組初始化的時候,做一些前置性的準備工作,以前常採用靜態建構函式這種不具有全域性性方法,侷限性很大,現在,這些都得到了完美解決。

如對您有價值,請推薦,您的鼓勵是我繼續的動力,在此萬分感謝。關注本人公眾號“碼客風雲”,享第一時間閱讀最新文章。

碼客風雲

 

相關文章