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

C# 泛型委派深度解析:Func/Action/Predicate 進階與事件模式最佳實務

     編輯:關於C語言

前言

委派是 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;

關鍵點:繫結時的泛型參數由目標方法簽章決定,而非宣告端。編譯器確保型別一致性。

三、泛型委派的實例化與方法群組轉換

C# 1.0 風格(顯式建構)

public delegate void Del<T>(T item);
public static void Notify(int i) { }

Del<int> m1 = new Del<int>(Notify);

C# 2.0+ 方法群組轉換(推薦)

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;
}

五、事件模式中的泛型委派:強型別 sender 優勢

傳統事件模式 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 為 objectsender 為強型別 Stack<T>
處理方法需 (Stack<T>)sender 轉型直接接收 Stack<T> stack
執行期才發現轉型錯誤編譯期保證型別正確
EventArgs 需向下轉型EventArgs 也是強型別

六、內建泛型委派:Func、Action、Predicate

大多數情況無需自訂委派,直接使用 BCL 內建:

委派簽章用途
Actionvoid ()無參無傳回
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)

這使得委派指派更具彈性,符合李氏替換原則直覺。

八、最佳實務總結

  1. 優先使用 Func/Action/Predicate,減少自訂委派宣告。
  2. 事件必須用 event 關鍵字,防止外部直接呼叫 Invoke 或覆寫委派鏈。
  3. 事件委派採用 (TSender, TEventArgs) 雙泛型參數,實現強型別 sender。
  4. 避免在委派中捕捉迴圈變數(C# 5.0+ foreach 已修復,但 for 迴圈仍需注意)。
  5. 大型委派鏈考慮效能:頻繁增減事件處理器可能導致委派陣列重新配置。

結語

泛型委派是 C# 函式式程式設計與事件驅動架構的基石。從 Func<T, TResult> 的無所不在,到自訂事件模式的強型別 sender,掌握泛型委派的宣告、繫結、變異數規則,能讓您設計出更安全、更具表達力、更易維護的 API。下次定義回呼或事件時,請嘗試用泛型委派替代 object 轉型,體驗編譯期型別安全的優雅。

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