首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 建構函式與解構函式深度解析:初始化順序、靜態建構、Finalize 與 GC 機制

     編輯:關於C#

前言:物件生命週期的守門人

建構函式負責「出生」初始化,解構函式負責「亡故」清理。在 .NET 托管環境下,記憶體回收由 GC 自動處理,解構函式轉型為非受控資源釋放的最終防線。本文詳細剖析實例/靜態建構函式執行順序、繼承鏈呼叫機制、解構函式與 Finalize 的關係、IDisposable 模式、GC 抑制終結化等核心主題。

一、實例建構函式

1.1 預設建構函式 vs 使用者定義

  • 類別未定義任何建構函式 → 編譯器自動產生無參數 public 預設建構函式,僅呼叫 base()。
  • 一旦定義任一建構函式,預設建構函式不再產生(若需無參數建構須自行定義)。

1.2 建構函式鏈結:this() 與 base()


class Base {
    public Base() { Console.WriteLine("Base()"); }
    public Base(string s) { Console.WriteLine($"Base({s})"); }
}
class Derived : Base {
    public Derived() : base() { Console.WriteLine("Derived()"); }        // 明確呼叫 base()
    public Derived(string s) : this() { Console.WriteLine($"Derived({s})"); } // 連結同類別其他建構函式
}

執行順序:欄位初始化 → base(...) → 本建構函式主體。

1.3 欄位初始化與建構函式的交織順序


class Base {
    public Base() { Print(); }
    public virtual void Print() { }
}
class Derived : Base {
    int x = 1;           // 欄位初始化器
    int y;
    public Derived() {
        y = -1;          // 建構函式主體
        Print();
    }
    public override void Print() => Console.WriteLine($"x={x}, y={y}");
}
new Derived(); 
// 輸出:x=1, y=0  (基類建構函式中呼叫虛擬方法,衍生類覆寫執行,y 尚未指派)
// 輸出:x=1, y=-1 (衍生類建構函式完成後再次呼叫)

鐵律:欄位初始化器在 base() 之前執行,建構函式主體在 base() 之後。

1.4 存取修飾符與單例/工廠模式

  • private 建構函式:防止外部實例化,配合靜態工廠方法或單例模式。
  • internal:限制同組件實例化。
  • protected:允許衍生類實例化,常用於抽象基類。

二、靜態建構函式

2.1 特性總覽

  • 關鍵字 static,無參數、無存取修飾符、不可呼叫。
  • 每個類別最多執行一次,由 CLR 保證執行緒安全。
  • 觸發時機(四大準則):
    1. 靜態欄位初始化器之後。
    2. 任何靜態成員被存取之前。
    3. 任何實例建立之前。
    4. 程式執行期間不確定確切時間,但保證在上述事件前。

2.2 靜態建構函式 vs 型別初始化器(.cctor)

IL 層面稱為 .cctor(class constructor),包含靜態欄位初始化器與靜態建構函式主體。標記 beforefieldinit 的型別(無靜態建構函式)執行時機更靈活;有靜態建構函式則移除該旗標,確保精確語意。

三、解構函式與 Finalize

3.1 語法與 IL 降級


// C# 原始碼
~MyClass() { Console.WriteLine("Destructor"); }

// IL 降級(編譯器自動產生)
protected override void Finalize() {
    try { Console.WriteLine("Destructor"); }
    finally { base.Finalize(); }
}
  • C# 禁止直接呼叫/覆寫 Finalize,強制使用 ~ClassName() 語法。
  • 解構函式不被繼承,衍生類若需清理必須自行定義 ~Derived()(編譯器自動在 finally 呼叫 base.Finalize())。
  • 無靜態解構函式,僅實例。

3.2 終結化佇列與 GC 雙重收集代價

  1. 物件擁有解構函式 → 配置時加入「終結化佇列」。
  2. GC 掃描到不可達物件在終結化佇列 → 移至「f-reachable 佇列」,標記為需終結化,**不立即回收記憶體**。
  3. 專用終結化執行緒執行 Finalize。
  4. 下一次 GC 才真正回收記憶體。

結論:終結化使物件至少經歷兩次 GC 週期,增加延遲與記憶體壓力,且執行順序不確定、執行緒不安全。

四、IDisposable 模式:確定性釋放非受控資源

4.1 標準實作範本


public sealed class ResourceHolder : IDisposable
{
    private IntPtr _handle; // 非受控資源
    private bool _disposed;

    public ResourceHolder() => _handle = NativeMethods.Acquire();

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this); // 告訴 GC 不需呼叫 Finalize
    }

    private void Dispose(bool disposing)
    {
        if (_disposed) return;
        if (disposing) {
            // 釋放受控資源(其他 IDisposable 物件)
        }
        // 釋放非受控資源
        if (_handle != IntPtr.Zero) {
            NativeMethods.Release(_handle);
            _handle = IntPtr.Zero;
        }
        _disposed = true;
    }

    ~ResourceHolder() => Dispose(false); // 終結化備援
}

4.2 using 語法糖與 await using (C# 8+)


using (var res = new ResourceHolder()) { /* ... */ } // 自動呼叫 Dispose()
// 或
await using var res = new AsyncResourceHolder(); // 非同步釋放

4.3 SafeHandle:框架級非受控資源封裝

  • 衍生 SafeHandleZeroOrMinusOneIsInvalid / SafeHandleMinusOneIsInvalid。
  • 覆寫 ReleaseHandle() 釋放邏輯。
  • 內建關鍵區段(Critical Finalization)保證即使 AppDomain 卸載也會執行。
  • 消除手寫 Finalize/Dispose 的錯誤風險。

五、GC 手動控制:何時呼叫 GC.Collect()?

  • 極少數情況:大量物件釋放後(如載入巨大資料集、切換場景),且確定後續無大量配置。
  • GC.Collect()、GC.WaitForPendingFinalizers()、GC.WaitForFullGCComplete()。
  • 頻繁呼叫嚴重降低效能,破壞 GC 分代啟發式演算法。

六、現代 C#:record、init、Primary Constructor (C# 12) 減少建構函式樣板


// C# 12 Primary Constructor
public class Person(string Name, int Age) 
{
    public string Name { get; init; } = Name;
    public int Age { get; init; } = Age;
}

// record 自動產生建構函式、不可變屬性、with-expression、相等性比較
public record Person(string Name, int Age);

結語

建構函式確立物件不變條件,解構函式/IDisposable 守護非受控資源。掌握初始化順序、靜態建構觸發時機、終結化佇列機制、Dispose 模式與 SafeHandle 最佳實務,並善用現代語法簡化樣板碼,即能寫出資源安全、效能穩定、易於維護的專業級 C# 程式碼。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved