微軟開放 .NET Framework 參考原始碼,讓我們得以窺探核心類別的實現細節。本文從 Delegate 委托切入,深度剖析其內部結構與運作機制,打破「委托就是函數指標」的迷思。
[Serializable]
[ClassInterface(ClassInterfaceType.AutoDual)]
[ComVisible(true)]
public abstract class Delegate : ICloneable, ISerializable
{
// 目標物件 (實例方法時為 this,靜態方法為 null)
internal object _target;
// 方法元資料 (MethodBase)
internal MethodBase _methodBase;
// 方法指標 (非托管指針)
internal IntPtr _methodPtr;
// 輔助指標 (開放靜態委托/實例方法調用時的 this 指標分離)
internal IntPtr _methodPtrAux;
// 編譯器生成的實例委托構造函數
protected Delegate(object target, string method) { ... }
// 編譯器生成的靜態委托構造函數
protected unsafe Delegate(Type target, string method) { ... }
// 私有預設構造函數,防止直接實例化
private Delegate() { }
}
雖語義上類似函數指標,但委托是 參考類型 (class),繼承自 Delegate 抽象基類。這解釋了為何委托可為 null、支援多播 (MulticastDelegate) 、實現 ICloneable/ISerializable。
宣告 public delegate void OnAction(int flag); 時,編譯器生成繼承自 MulticastDelegate 的密封類別:
.class public auto ansi sealed OnAction extends [mscorlib]System.MulticastDelegate
{
.method public hidebysig specialname rtspecialname instance void .ctor(object, native int) runtime managed { }
.method public hidebysig instance void Invoke(int32) runtime managed { }
.method public hidebysig instance class [mscorlib]System.IAsyncResult BeginInvoke(int32, class [mscorlib]System.AsyncCallback, object) runtime managed { }
.method public hidebysig instance void EndInvoke(class [mscorlib]System.IAsyncResult) runtime managed { }
}
構造函數簽名 (object, native int) 對應 _target 與 _methodPtr。開發者無法顯式繼承 Delegate/MulticastDelegate,這完全由編譯器與 CLR 內部協作完成。
構造函數調用 BindToMethodName / BindToMethodInfo,標註 [MethodImplAttribute(MethodImplOptions.InternalCall)]:
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private extern bool BindToMethodName(object target, RuntimeTypeHandle methodType, string method, DelegateBindingFlags flags);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private extern bool BindToMethodInfo(object target, RuntimeMethodHandle method, RuntimeTypeHandle type, DelegateBindingFlags flags);
這意味著「將方法綁定到委托實例」的核心邏輯在 **CLR 原生代碼** 中實現,非托管層面完成方法指標提取、簽名驗證、開放/封閉委托判斷等關鍵操作。
Delegate.CreateDelegate 工廠方法所有 11 個重載最終均調用:
Delegate d = InternalAlloc(type.TypeHandle);
if (!d.BindToMethodInfo(firstArgument, method.MethodHandle, method.DeclaringType.TypeHandle, DelegateBindingFlags.RelaxedSignature))
throw new ArgumentException(...);
InternalAlloc 同為 CLR 內部方法,負責分配委托實例記憶體。
| 欄位 | 用途 |
|---|---|
_target | 目標實例 (實例方法) 或 null (靜態方法) |
_methodBase | MethodBase 反射物件,供 Method 屬性返回 |
_methodPtr | 非托管方法入口指標,Invoke 直接調用 |
_methodPtrAux | 輔助指標:靜態方法時存入真實方法指標,_methodPtr 指向 thunk 移除 this 指標 |
ICloneable:淺複製委托實例 (複製引用),極少直接使用。ISerializable:支援委托跨應用程式域/序列化傳遞。但這也帶來副作用:包含事件 (編譯器生成 add/remove 存取器委托) 的可序列化類別,若事件未標記 [NonSerialized],序列化時會嘗試序列化委托鏈中的目標物件,導致「未標記序列化」異常。實務上需謹慎處理。
委托本質是 **CLR 與編譯器深度協作的參考類型封裝**,而非簡單的函數指標。其核心能力——方法綁定、調用分派、多播鏈管理——均在 CLR 非托管層實現。C# 語法只是薄薄的語法糖外殼。理解這一機制,有助於正確使用委托、避免序列化陷阱、掌握非同步模式 (BeginInvoke/EndInvoke) 的底層運作。