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

C# 建構函式與解構函式完整指南:從基礎原理到資源管理實戰

     編輯:關於C語言

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

在 C# 物件導向程式設計中,建構函式與解構函式分別掌控物件的誕生與終結。雖然語法簡單,但涉及記憶體管理、資源釋放、繼承鏈初始化順序等核心機制。本文系統整理建構函式的四種類型、繼承情境下的執行順序、解構函式與垃圾收集器(GC)的互動關係,以及非受控資源釋放的標準模式,幫助開發者避開常見陷阱。

一、建構函式核心原理

命名規則與特性

  • 名稱必須與類別完全相同
  • 無回傳型別(連 void 都不能寫)
  • 物件建立時自動執行,確保初始化邏輯不被遺漏
  • 若未自訂,編譯器提供無參數預設建構函式

四種類型詳細解析

1. 預設建構函式

class TestClass
{
    // 編譯器自動產生,等同於:
    public TestClass() : base() { }
}

僅在類別未定義任何建構函式時存在。一旦自訂任何建構函式,預設建構函式即消失。

2. 實例建構函式(含多載)

class Point
{
    public double X, Y;
    
    public Point()           // 無參數
    { X = 0; Y = 0; }
    
    public Point(double x, double y) // 兩參數
    { X = x; Y = y; }
}

支援多載,提供多種初始化路徑。注意:定義任一建構函式後,若仍需無參數版本,必須顯式撰寫。

3. 靜態建構函式

class Employee
{
    private static DataSet _ds;
    
    static Employee() // 無修飾詞、無參數、不可呼叫
    {
        _ds = new DataSet(); // 初始化靜態資料
    }
}
  • 類別載入時由 CLR 自動呼叫一次(執行緒安全)
  • 僅能初始化靜態欄位,不可存取實例成員
  • 常用於單例模式、靜態資源初始化

4. 私有建構函式

class Singleton
{
    private Singleton() { } // 防止外部實例化
    public static Singleton Instance { get; } = new Singleton();
}

配合靜態工廠方法實現單例、工廠模式或純靜態工具類別。

二、繼承體系中的建構函式執行順序

核心原則:基類優先,由外而內

class A { public A() { Console.WriteLine("A()"); } public A(int i) { Console.WriteLine($"A({i})"); } }
class B : A 
{ 
    public B() { Console.WriteLine("B()"); }
    public B(int i) : base(i) { Console.WriteLine($"B({i})"); } // 顯式呼叫基類有參建構函式
    public B(int i, int j) : base(i) { Console.WriteLine($"B({i},{j})"); }
}

執行流程圖解

  1. 衍生類建構函式進入
  2. 隱式/顯式呼叫基類建構函式(base() 或 base(參數))
  3. 基類建構函式執行完成
  4. 衍生類成員欄位初始化(初始化運算式)
  5. 衍生類建構函式主體執行

關鍵規則

  • 若基類無無參建構函式,衍生類必須在所有建構函式中透過 base(參數) 顯式呼叫
  • 成員物件(Composition)初始化發生在基類建構函式之後、衍生類建構函式之前
  • 建構函式中避免呼叫虛擬方法(衍生類尚未完全初始化)

三、解構函式與垃圾收集器深度解析

語法與限制

class ResourceHolder
{
    ~ResourceHolder() // 無修飾詞、無參數、不可呼叫、無法多載
    {
        // 清理非受控資源
    }
}

GC 運作機制與解構函式關係

記憶體分代與回收流程

  • 第 0 代:新建物件,頻繁回收
  • 第 1 代:倖存第 0 代回收的物件
  • 第 2 代:長期存活物件(大型物件直接進第 2 代)

有解構函式的物件特殊處理

  1. 物件變成不可觸達
  2. GC 將其放入 Finalization Queue(終結佇列)
  3. 專用 Finalizer 執行緒呼叫 Finalize()(即 ~ClassName())
  4. 物件從 Finalization Queue 移至 Freachable Queue(可釋佇列)
  5. 下一次 GC 才真正回收記憶體

效能代價:有解構函式的物件至少經歷兩次 GC 週期才被回收,且 Finalizer 執行緒阻塞會影響整個應用程式。

受控 vs 非受控資源

資源類型範例釋放方式
受控資源記憶體、陣列、List、DataSet、其他 Managed 物件GC 自動回收,無需介入
非受控資源檔案控制代碼、資料庫連線、GDI+ 物件、COM 物件、記憶體指標必須手動釋放,通常在解構函式或 Dispose() 中

四、標準 Dispose 模式實作(IDisposable)

解構函式是最後防線,Dispose 模式才是資源釋放的主流程:

public class ResourceWrapper : IDisposable
{
    private IntPtr _unmanagedHandle = IntPtr.Zero;
    private bool _disposed = false;
    private SafeHandle _safeHandle = new SafeFileHandle(IntPtr.Zero, true); // 受控包裝器
    
    // 公開 Dispose
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this); // 告訴 GC 不需呼叫 Finalize
    }
    
    // 核心邏輯:disposing=true 來自 Dispose(),false 來自 Finalizer
    protected virtual void Dispose(bool disposing)
    {
        if (_disposed) return;
        
        if (disposing)
        {
            // 釋放受控資源
            _safeHandle?.Dispose();
        }
        
        // 釋放非受控資源(無論 disposing 真偽)
        if (_unmanagedHandle != IntPtr.Zero)
        {
            CloseHandle(_unmanagedHandle);
            _unmanagedHandle = IntPtr.Zero;
        }
        
        _disposed = true;
    }
    
    // 解構函式作為安全網
    ~ResourceWrapper()
    {
        Dispose(false);
    }
    
    [DllImport("kernel32.dll")]
    private static extern bool CloseHandle(IntPtr hObject);
}

使用端最佳實踐

// using 區塊確保 Dispose 被呼叫
using (var resource = new ResourceWrapper())
{
    // 使用資源
} // 隱式呼叫 Dispose()

// 或手動控制
var resource = new ResourceWrapper();
try { /* 使用 */ }
finally { resource.Dispose(); }

五、常見錯誤與避坑指南

  1. 在建構函式中拋出例外:物件未完全建立,解構函式不會執行,非受控資源洩漏 → 使用工廠方法或兩階段初始化
  2. 解構函式中存取其他受控物件:該物件可能已被 Finalize → 僅清理非受控資源
  3. 遺漏 GC.SuppressFinalize:造成雙重清理與效能損耗
  4. 密封類別仍宣告虛擬 Dispose:sealed 類別無需 protected virtual Dispose(bool)
  5. 直接呼叫解構函式:語法錯誤,只能透過 Dispose() 或 GC 觸發

六、現代 C# 改進:SafeHandle 與模式匹配

  • SafeHandle 衍生類別(SafeFileHandle, SafeRegistryHandle 等)自動實作 IDisposable 並處理 Finalize 邏輯
  • C# 8.0+ using 宣告簡化範圍管理:using var fs = File.OpenRead("x");
  • 避免直接使用 IntPtr 操作非受控記憶體,改用 Marshal 類別或 Span/Memory

總結

建構函式確保物件「生而正確」,解構函式與 IDisposable 確保物件「死而無憾」。掌握繼承鏈初始化順序、GC 代際機制、Dispose 標準模式,是寫出健壯、高效能 C# 程式的關鍵。記住核心原則:受控資源交給 GC,非受控資源用 Dispose 模式主動釋放,解構函式僅作為最後安全網。

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