關於模塊或者程序集初始化工做一直是C#的一個痛點,微軟內部外部都有大量的報告反應不少客戶一直被這個問題困擾,這還不算沒有統計上的客戶。那麼解決這個問題,還有基於什麼樣的考慮呢?app
在庫加載的時候,能以最小的開銷、無需用戶顯式調用任何接口,使客戶作一些指望的和一次性的初始化。函數
當前靜態構造函數方法的一個最大的問題是運行時會對帶有靜態構造函數的類型作一些額外的檢查。這是由於要決定靜態構造函數是否須要被運行所必須的一步,可是這個又有着顯著的開銷影響。spa
使源代碼生成器在不須要用戶顯式調用一些東西的狀況下能運行一些全局的初始化邏輯。設計
C# 9.0將模塊初始化器設計爲一個Attribute,用這個Attribute來修飾進行模塊初始化邏輯的方法,就實現了模塊初始化功能。這個Attribute被命名爲ModuleInitializerAttribute,具體定義以下:3d
using System; namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public sealed class ModuleInitializerAttribute : Attribute { } }
若是要使用模塊初始化器,你只要將ModuleInitializerAttribute用在符合下面要求的方法上就能夠了。code
該方法必須使靜態的、無參的、返回值爲void的函數。token
該方法不能是泛型或者包含在泛型類型裏接口
該方法必須是可從其所在模塊裏訪問的。也就是說,方法的有效訪問符必須是internal或者public,不能是局部方法。ip
using System.Runtime.CompilerServices; class MyClass { [ModuleInitializer] internal static void Initializer() { // ... } }
被修飾爲ModuleInitializerAttribute的靜態方法會被編譯器在編譯時,在全局的靜態構造函數中生成此代碼調用。若是有多個被修飾爲初始化器的函數,則每一個函數生成一個初始化器代碼調用,這些初始化器代碼調用代碼會按照必定的順序(類型名稱順序和代碼順序)生成。當模塊在被加載時,全局靜態構造函數開始執行,從而完成模塊代碼初始化工做。開發
模塊初始化器與靜態構造函數之間有着必定的關聯影響。由於模塊初始化器是一個靜態方法,於是其被調用執行前,必然會引發其所處類型的靜態構造函數的執行。請參考下列示例:
static class ModuleInit { static ModuleInit() { //先執行 Console.WriteLine("ModuleInit靜態構造函數 cctor"); } [ModuleInitializer] internal static void Initializer() { //在靜態構造函數執行後才執行 Console.WriteLine("模塊初始化器"); } }
在一個模塊中指定多個模塊初始化器的時候,他們之間的順序也是一個值得注意的問題。以上這些問題的存在,就要求咱們注意如下幾點:
在指定了模塊初始化器的類型中,不要在靜態構造函數中,寫與模塊初始化器中代碼有着順序依賴代碼,最好的就是不要使用靜態構造函數。
多個模塊初始化器之間的代碼,也不要有任何依賴關係,保持各個初始化器代碼的獨立性。
平常開發中,咱們一般須要在模塊初始化的時候,作一些前置性的準備工做,之前常採用靜態構造函數這種不具備全局性方法,侷限性很大,如今,這些都獲得了完美解決。