建構函式負責物件誕生時的初始化,解構函式 (Finalizer) 則是非受控資源釋放的最後防線。本文系統說明各類建構函式語意、繼承鏈執行順序、GC 與 Finalize 機制、IDisposable 模式、以及常見陷阱與最佳實務。
public class Point
{
public int X { get; set; }
public int Y { get; set; }
// 無參數建構函式
public Point() : this(0, 0) { } // 鏈結呼叫
// 參數化建構函式
public Point(int x, int y)
{
X = x; Y = y;
}
// 運算式主體建構函式 (C# 7+)
public Point(double polarR, double polarTheta) =>
(X, Y) = ((int)(polarR * Math.Cos(polarTheta)), (int)(polarR * Math.Sin(polarTheta)));
}
static,無參數、無存取修飾詞
public class Database
{
private static readonly string _connectionString;
private static readonly SemaphoreSlim _semaphore = new(1);
static Database()
{
_connectionString = LoadConfig(); // 複雜初始化邏輯
Console.WriteLine("靜態建構函式執行");
}
}
// Record 自動產生主要建構函式
public record Person(string Name, int Age);
// 類別主要建構函式 (C# 12)
public class Employee(string name, int age)
{
public string Name { get; } = name;
public int Age { get; } = age;
}
base(...) 或 this(...)base() (呼叫基底類別無參建構函式)
class Base
{
public Base() { Console.WriteLine("Base()"); }
public Base(int x) { Console.WriteLine($"Base({x})"); }
}
class Derived : Base
{
private int _y = InitY(); // 1. 衍生欄位初始化
private static int InitY() { Console.WriteLine("Derived field init"); return 10; }
public Derived() : base(5) // 2. 顯式呼叫 base(5)
{
Console.WriteLine("Derived()"); // 4. 衍生建構函式本體
}
}
// 輸出順序:
// Derived field init
// Base(5)
// Derived()
class Base
{
public Base() { Initialize(); } // 危險!
protected virtual void Initialize() { Console.WriteLine("Base Initialize"); }
}
class Derived : Base
{
private readonly List _items = new(); // 欄位初始化
protected override void Initialize()
{
Console.WriteLine("Derived Initialize");
_items.Add("test"); // NullReferenceException!_items 尚未初始化
}
}
鐵律:建構函式中絕不呼叫虛擬方法 (直接或間接)。
public class ResourceHolder : IDisposable
{
private IntPtr _unmanagedHandle;
private bool _disposed;
// 解構函式 (Finalizer)
~ResourceHolder()
{
Dispose(false); // 僅釋放非受控資源
}
// 公用 Dispose
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this); // 告訴 GC 不需呼叫 Finalize
}
// 核心釋放邏輯
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing)
{
// 釋放受控資源 (Managed)
// managedResource?.Dispose();
}
// 釋放非受控資源 (Unmanaged)
if (_unmanagedHandle != IntPtr.Zero)
{
NativeMethods.CloseHandle(_unmanagedHandle);
_unmanagedHandle = IntPtr.Zero;
}
_disposed = true;
}
}
Finalize (~ClassName)GC.SuppressFinalize 導致重複釋放
public class DisposableBase : IDisposable
{
private bool _disposed;
// 公開 Dispose
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
// 核心虛擬方法,衍生類別覆寫
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing)
{
// 釋放受控資源
ReleaseManagedResources();
}
// 釋放非受控資源
ReleaseUnmanagedResources();
_disposed = true;
}
// 解構函式:僅釋放非受控資源
~DisposableBase() => Dispose(false);
// 衍生類別實作
protected virtual void ReleaseManagedResources() { }
protected virtual void ReleaseUnmanagedResources() { }
// 防護:公開方法檢查是否已釋放
protected void ThrowIfDisposed()
{
if (_disposed) throw new ObjectDisposedException(GetType().Name);
}
}
// 使用範例
public class FileProcessor : DisposableBase
{
private FileStream _stream;
public void Open(string path)
{
ThrowIfDisposed();
_stream = File.OpenRead(path);
}
protected override void ReleaseManagedResources()
{
_stream?.Dispose();
_stream = null;
}
}
.NET 2.0+ 提供 SafeHandle 封裝非受控控制碼,自動處理 Finalize、參考計數、關鍵區段:
public class SafeFileHandle : SafeHandleZeroOrMinusOneIsInvalid
{
public SafeFileHandle(IntPtr handle, bool ownsHandle) : base(ownsHandle) { SetHandle(handle); }
protected override bool ReleaseHandle() => NativeMethods.CloseHandle(handle);
}
// 使用
using var handle = new SafeFileHandle(NativeMethods.CreateFile(...), true);
// 區塊結束自動釋放,無需寫 Finalizer
| 陷阱 | 後果 | 解決 |
|---|---|---|
| 建構函式呼叫虛擬方法 | 存取未初始化欄位 | 建構函式只呼叫非虛擬/私有方法 |
| 衍生類別未呼叫 base() | 編譯錯誤或基底未初始化 | 顯式呼叫 base(參數) |
| 解構函式釋放受控資源 | 受控物件可能已被 GC 回收 | Dispose(false) 只釋放非受控 |
| 忘記 GC.SuppressFinalize | 重複釋放、效能損耗 | Dispose() 最後呼叫 |
| 非受控資源未實作 IDisposable | 資源洩漏 (檔案鎖、連線) | 實作標準 Dispose 模式 |
| Finalizer 中拋出例外 | 進程終止 / 執行緒中止 | Finalizer 內全部 try-catch |
IDisposable 實作決定性釋放using / SafeHandle / 容器管理生命週期,極少情況需手寫 Finalizer