靜態建構函式 (C# 程式設計手冊)
靜態建構函式用來初始化任何靜態資料,或執行只需要執行一次的特定動作。 在建立第一個執行個體或參考任何靜態成員之前,會自動進行呼叫。 靜態建構函式最多會呼叫一次。
class SimpleClass
{
// Static variable that must be initialized at run time.
static readonly long baseline;
// Static constructor is called at most one time, before any
// instance constructor is invoked or member is accessed.
static SimpleClass()
{
baseline = DateTime.Now.Ticks;
}
}
有數個動作屬於靜態初始化的一部分。 這些動作會依下列順序進行:
- 靜態欄位設定為 0。 執行階段通常會執行此初始化。
- 靜態欄位初始設定式會執行。 最衍生型別中靜態欄位初始設定式會執行。
- 基底類型靜態欄位初始設定式會執行。 從直接基底開始,通過每個基底型別到 System.Object 的靜態欄位初始設定式。
- 所有靜態建構函式都會執行。 任何靜態建構函式,從最終的基底類別 Object.Object 透過類型執行到每個基底類別。 未指定靜態建構函式執行的順序。 不過,階層中的所有靜態建構函式都會在建立任何執行個體之前執行。
重要
在建立任何執行個體之前,靜態建構函式執行的規則有一個重要的例外狀況。 如果靜態欄位初始設定式建立該類型的執行個體,該初始設定式會在靜態建構函式執行之前執行 (包括對執行個體建構函式的任何呼叫)。 這在單一模式中最常見,如下列範例所示:
public class Singleton
{
// Static field initializer calls instance constructor.
private static Singleton instance = new Singleton();
private Singleton()
{
Console.WriteLine("Executes before static constructor.");
}
static Singleton()
{
Console.WriteLine("Executes after instance constructor.");
}
public static Singleton Instance => instance;
}
模組初始設定式可以是靜態建構函式的替代方案。 如需詳細資訊,請參閱模組初始設定式的規格 (英文)。
備註
靜態建構函式具有下列屬性:
- 靜態建構函式不會接受存取修飾詞,也不會包含參數。
- 類別或結構只能有一個靜態建構函式。
- 靜態建構函式無法繼承或多載。
- 您無法直接呼叫靜態建構函式,且其是設計成只由通用語言執行平台 (CLR) 呼叫的。 系統會自動呼用之。
- 使用者無法控制在程式中執行靜態建構函式的時間。
- 靜態建構函式會自動呼叫。 它會先初始化類別,再建立第一個執行個體,或是參考該類別 (非其基底類別) 中宣告的任何靜態成員。 靜態建構函式在執行個體建構函式之前執行。 如果靜態欄位變數初始設定式存在於靜態建構函式的類別中,其會按照類別宣告中出現的文字順序執行。 初始設定式會在靜態建構函式之前立即執行。
- 如果您不提供靜態建構函式來初始化靜態欄位,則所有靜態欄位都會初始化為其預設值,如 C# 型別的預設值中所列。
- 如果靜態建構函式擲回例外狀況,執行階段不會叫用之第二次,而類型會在應用程式定義網域的存留期內保持未初始化。 在多數情況下,當靜態建構函示無法具現化型別時,或靜態建構函式內出現未處理的例外狀況時,會擲回 TypeInitializationException 例外狀況。 對於在原始程式碼中未明確定義的靜態建構函式,進行疑難排解可能需要檢查中繼語言 (IL) 程式碼。
- 靜態建構函式的存在可能阻止 BeforeFieldInit 型別屬性的新增。 這會限制執行階段最佳化。
- 宣告為
static readonly
的欄位,只能指派為其宣告的一部分,或在靜態建構函式中指派。 當不需要明確靜態建構函式時,請在宣告時初始化靜態欄位,而不要透過靜態建構函式,以獲得更好的執行階段最佳化。 - 執行階段在單一應用程式網域中呼叫靜態建構函式不超過一次。 該呼叫會根據類別的特定類型,在鎖定區域中進行。 靜態建構函式主體中不需要額外的鎖定機制。 若要避免死結的風險,請勿封鎖靜態建構函式和初始設定式中的目前執行緒。 例如,不要等候工作、執行緒、等候控制代碼或事件、不取得鎖定,也不會執行封鎖平行作業,例如平行迴圈、
Parallel.Invoke
和平行 LINQ 查詢。
注意
雖然無法直接存取明確靜態建構函式,但應記錄其存在,以協助對初始化例外狀況進行疑難排解。
使用方式
- 靜態建構函式的一般用法為:當類別正在使用記錄檔,且建構函式用來將項目寫入這個檔案時。
- 如果建構函式可以呼叫
LoadLibrary
方法,靜態建構函式在建立 unmanaged 程式碼的包裝函式類別時也很有用。 - 若要對在編譯階段無法透過型別參數限制式檢查的型別參數,強制執行執行階段檢查,在靜態建構函式中這麼做是方便的選擇。
範例
在此範例中,Bus
類別具有靜態建構函式。 建立 Bus
的第一個執行個體 (bus1
) 時,即會叫用靜態建構函式來初始化類別。 範例輸出可確認即使建立了兩個 Bus
執行個體,靜態建構函式也只執行一次,而且它是在執行個體建構函式執行之前執行。
public class Bus
{
// Static variable used by all Bus instances.
// Represents the time the first bus of the day starts its route.
protected static readonly DateTime globalStartTime;
// Property for the number of each bus.
protected int RouteNumber { get; set; }
// Static constructor to initialize the static variable.
// It is invoked before the first instance constructor is run.
static Bus()
{
globalStartTime = DateTime.Now;
// The following statement produces the first line of output,
// and the line occurs only once.
Console.WriteLine("Static constructor sets global start time to {0}",
globalStartTime.ToLongTimeString());
}
// Instance constructor.
public Bus(int routeNum)
{
RouteNumber = routeNum;
Console.WriteLine("Bus #{0} is created.", RouteNumber);
}
// Instance method.
public void Drive()
{
TimeSpan elapsedTime = DateTime.Now - globalStartTime;
// For demonstration purposes we treat milliseconds as minutes to simulate
// actual bus times. Do not do this in your actual bus schedule program!
Console.WriteLine("{0} is starting its route {1:N2} minutes after global start time {2}.",
this.RouteNumber,
elapsedTime.Milliseconds,
globalStartTime.ToShortTimeString());
}
}
class TestBus
{
static void Main()
{
// The creation of this instance activates the static constructor.
Bus bus1 = new Bus(71);
// Create a second bus.
Bus bus2 = new Bus(72);
// Send bus1 on its way.
bus1.Drive();
// Wait for bus2 to warm up.
System.Threading.Thread.Sleep(25);
// Send bus2 on its way.
bus2.Drive();
// Keep the console window open in debug mode.
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Sample output:
Static constructor sets global start time to 3:57:08 PM.
Bus #71 is created.
Bus #72 is created.
71 is starting its route 6.00 minutes after global start time 3:57 PM.
72 is starting its route 31.00 minutes after global start time 3:57 PM.
*/