委派是 C# 型別安全的函式指標。泛型委派讓委派簽章能參數化,配合 Func<T, TResult>、Action<T>、Predicate<T> 等內建委派,極大簡化了程式碼。本文從自訂泛型委派、委派繫結、泛型類別中的委派、到事件模式中的強型別優勢,全方位解析泛型委派實戰應用。
委派分兩大類:
Func<T, TResult>、自訂 delegate TResult MyDelegate<T, TResult>(T arg)Action<T>、delegate void EventDelegate<T>(T sender, EventArgs e)// 有傳回值的泛型委派
public delegate string GenericDelegate<T, S>(T title, S author);
// 事件用泛型委派(慣例:sender, eventArgs)
public delegate void GenericEventDelegate<E, P>(E sender, P args);
public class GenericDelegateClass<V, F>
{
// 委派欄位
public GenericDelegate<V, F> DelegateField;
// 事件(建議使用 event 關鍵字限制外部只能 += / -=)
public event GenericEventDelegate<V, F> GenericEvent;
// 呼叫委派欄位
public string GetValues(V title, F author)
{
return DelegateField?.Invoke(title, author); // C# 6.0 null 條件運算子
}
// 引發事件
public void InvokeEvent(V name, F address)
{
GenericEvent?.Invoke(name, address);
}
}
// 目標方法
public string DelegateReturn<T, S>(T title, S author)
=> $"{title}{author}";
private void GenericEvent<V, F>(V name, F address)
{ /* 事件處理邏輯 */ }
// 繫結
var gd = new GenericDelegateClass<string, string>();
// 委派欄位繫結:方法群組轉換(無需 new Delegate())
gd.DelegateField = DelegateReturn; // 編譯器自動推斷型別參數
// 事件繫結
gd.GenericEvent += GenericEvent;
關鍵點:繫結時的泛型參數由目標方法簽章決定,而非宣告端。編譯器確保型別一致性。
public delegate void Del<T>(T item);
public static void Notify(int i) { }
Del<int> m1 = new Del<int>(Notify);
Del<int> m2 = Notify; // 簡潔、型別安全、可讀性高
巢狀委派可直接使用外層類別的型別參數:
class Stack<T>
{
T[] items;
int index;
// 使用類別的 T
public delegate void StackDelegate(T[] items);
}
private static void DoWork(float[] items) { }
public static void TestStack()
{
var s = new Stack<float>();
// 必須指定外層類別的型別引數
Stack<float>.StackDelegate d = DoWork;
}
傳統事件模式 EventHandler 的 sender 為 object,需轉型。泛型委派讓 sender 成為具體型別:
// 定義泛型事件委派
public delegate void StackEventHandler<T, U>(T sender, U eventArgs);
class Stack<T>
{
public class StackEventArgs : EventArgs { }
// 事件型別:sender 精確為 Stack<T>
public event StackEventHandler<Stack<T>, StackEventArgs> StackChanged;
protected virtual void OnStackChanged(StackEventArgs e)
{
StackChanged?.Invoke(this, e); // this 型別為 Stack<T>,完美匹配
}
}
class SampleClass
{
// 處理方法:sender 直接是 Stack<T>,無需轉型
public void HandleStackChange<T>(Stack<T> stack, Stack<T>.StackEventArgs args)
{
Console.WriteLine($"Stack<{typeof(T).Name}> 變更");
}
}
public static void Test()
{
var s = new Stack<double>();
var o = new SampleClass();
// 方法群組轉換自動推斷 T 為 double
s.StackChanged += o.HandleStackChange;
}
| 傳統 EventHandler | 泛型委派事件 |
|---|---|
sender 為 object | sender 為強型別 Stack<T> |
處理方法需 (Stack<T>)sender 轉型 | 直接接收 Stack<T> stack |
| 執行期才發現轉型錯誤 | 編譯期保證型別正確 |
| EventArgs 需向下轉型 | EventArgs 也是強型別 |
大多數情況無需自訂委派,直接使用 BCL 內建:
| 委派 | 簽章 | 用途 |
|---|---|---|
Action | void () | 無參無傳回 |
Action<T> | void (T) | 1 參無傳回 |
Action<T1, T2, T3, T4> | void (T1, T2, T3, T4) | 最多 16 參數 |
Func<TResult> | TResult () | 無參有傳回 |
Func<T, TResult> | TResult (T) | 1 參有傳回 |
Func<T1, T2, TResult> | TResult (T1, T2) | 最多 16 參數 + 傳回 |
Predicate<T> | bool (T) | 條件判斷(如 List.Find) |
var numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// Predicate: 尋找偶數
Predicate<int> isEven = n => n % 2 == 0;
int firstEven = numbers.Find(isEven); // 2
// Func: 投影轉換
Func<int, string> toHex = n => n.ToString("X");
var hexStrings = numbers.ConvertAll(toHex); // ["1", "2", "3", ..., "A"]
// Action: 列印
Action<int> print = n => Console.Write($"{n} ");
numbers.ForEach(print);
C# 4.0 引入委派的 in(逆變異數)與 out(共變異數)修飾詞:
// 共變異數:傳回型別可為衍生類別
public delegate out TResult Func<in T, out TResult>(T arg);
// 逆變異數:參數型別可為基底類別
public delegate void Action<in T>(T arg);
Func<object, string> f1 = o => o.ToString();
Func<string, object> f2 = f1; // 合法!參數逆變異數、傳回共變異數
Action<string> a1 = s => Console.WriteLine(s);
Action<object> a2 = a1; // 合法!參數逆變異數(object 可接受 string)
這使得委派指派更具彈性,符合李氏替換原則直覺。
event 關鍵字,防止外部直接呼叫 Invoke 或覆寫委派鏈。(TSender, TEventArgs) 雙泛型參數,實現強型別 sender。泛型委派是 C# 函式式程式設計與事件驅動架構的基石。從 Func<T, TResult> 的無所不在,到自訂事件模式的強型別 sender,掌握泛型委派的宣告、繫結、變異數規則,能讓您設計出更安全、更具表達力、更易維護的 API。下次定義回呼或事件時,請嘗試用泛型委派替代 object 轉型,體驗編譯期型別安全的優雅。