委託 是 C# 將方法「一等公民化」的基石,事件 是基於委託的發布-訂閱標準化,Lambda 表達式 讓函數式編程風格無縫融入。本文梳理三者**從 .NET 1.0 到現代版本的演進脈絡**,剖析協變/逆變、匿名方法、表達式樹、async/await 等關鍵節點。
System.Delegate (abstract)
└─ System.MulticastDelegate (abstract, sealed by runtime)
├─ 用戶定義委託類型 (編譯器生成密封類)
│ ├─ Invoke() // 同步調用
│ ├─ BeginInvoke() // APM 異步開始
│ └─ EndInvoke() // APM 異步結束
└─ 內置委託
├─ Action / Action<T...> // void 返回
├─ Func<TResult> / Func<T...,TResult> // 有返回值
├─ Predicate<T> // Func<T,bool> 別名
├─ EventHandler / EventHandler<TEventArgs> // 標準事件模式
└─ Comparison<T> // 排序比較
| 版本 | 特性 | 影響 |
|---|---|---|
| .NET 1.0 (C# 1.0) | 委託、事件 | 類型安全的函數指針、標準發布-訂閱模式 |
| .NET 2.0 (C# 2.0) | 匿名方法 delegate { } | 內聯代碼塊,捕獲外部變量 (閉包雛形)、簡化單次回調 |
| .NET 3.5 (C# 3.0) | Lambda 表達式 =>、表達式樹 Expression<TDelegate> | 語法極簡、LINQ 基石、動態查詢/編譯、EDM 映射 |
| .NET 4.0 (C# 4.0) | 委託協變/逆變 in/out | Func<in T, out TResult>、Action<in T> 類型安全轉換 |
| .NET 4.5 (C# 5.0) | async/await | 異步編程模式統一,Task 替代 BeginInvoke |
| C# 6.0+ | 表達式體成員、nameof、空條件委託調用 ?.Invoke() | 語法糖提升可讀性與安全性 |
| C# 9.0+ | 函數指針 delegate*、靜態匿名函數 static () => | 高性能互操作、避免閉包分配 |
Action a = () => Console.WriteLine("1");
a += () => Console.WriteLine("2");
a(); // 順序調用 1 → 2
// 內部: _invocationList 鏈表,異常中斷後續調用
// 協變: 返回類型可隱式轉換為基類
Func<Dog> fDog = () => new Dog();
Func<Animal> fAnimal = fDog; // 合法: out TResult
// 逆變: 參數類型可隱式轉換為派生類
Action<Animal> aAnimal = a => Console.WriteLine(a);
Action<Dog> aDog = aAnimal; // 合法: in T
Delegate d = (Func<int,int>)(x => x * 2);
object result = d.DynamicInvoke(5); // 返回 10 (object)
// 適用: 插件系統、腳本引擎、運行時綁定
// 1. 事件參數類
public class OddFoundEventArgs : EventArgs {
public int Number { get; }
public OddFoundEventArgs(int n) => Number = n;
}
// 2. 發布者
public class OddFinder {
// 標準事件模式: EventHandler
public event EventHandler<OddFoundEventArgs> OddFound;
public void Find(int from, int to) {
for (int i = from; i <= to; i++) {
if (i % 2 != 0) {
// 安全觸發: 拷貝委託引用防併發移除
var handler = OddFound;
handler?.Invoke(this, new OddFoundEventArgs(i));
}
}
}
}
// 3. 訂閱者
var finder = new OddFinder();
finder.OddFound += (s, e) => Console.WriteLine($"發現奇數: {e.Number}");
finder.Find(1, 10);
event 關鍵字:限制外部只能 +=/-=,**不能直接賦值/調用**,保護封裝。| 形態 | 語法 | 編譯目標 | 典型用途 |
|---|---|---|---|
| 表達式 Lambda | x => x * 2 | 委託 或 表達式樹 Expression<Func<int,int>> | LINQ 查詢、動態構建邏輯、EDM 映射 |
| 語句 Lambda | x => { var t=x*2; return t; } | 委託 (不可轉表達式樹) | 多行邏輯、副作用操作 |
| 異步 Lambda | async x => await Fetch(x) | Func<T, Task<TResult>> | 異步管道、並發處理 |
// 1. 創建表達式樹 (需 using System.Linq.Expressions)
Expression<Func<int, int, int>> expr = (a, b) => a + b;
// 2. 編譯為委託 (運行時生成 IL)
Func<int, int, int> compiled = expr.Compile();
int result = compiled(3, 5); // 8
// 3. 遍歷/修改樹 (動態查詢核心)
var binary = (BinaryExpression)expr.Body;
Console.WriteLine(binary.NodeType); // Add
Console.WriteLine(binary.Left); // a
Console.WriteLine(binary.Right); // b
應用:Entity Framework 將 Expression 翻譯為 SQL;動態篩選器構建器;規則引擎。
| 場景 | 傳統委託/Lambda | 現代替代 (C# 9+/11+) |
|---|---|---|
| 熱路徑頻繁調用 | 委託調用開銷 (間接跳轉、閉包分配) | delegate* 函數指針 (非托管調用約定,零分配) |
| 避免閉包捕獲 | 捕獲變量 → 顯示類分配 | static () => ... 靜態匿名函數 (無 this、無捕獲) |
| 異步回調 | BeginInvoke/EndInvoke (APM) | async/await + ValueTask (TAP) |
| 互操作回調 | Marshal.GetFunctionPointerForDelegate | delegate* unmanaged<...> 直接轉函數指針 |
委託賦予 C#「函數即值」的能力,事件建立了鬆耦合通信標準,Lambda 讓函數式思維落地,表達式樹打通「代碼與數據」邊界。掌握演進脈絡,才能在遺留維護與現代架構間游刃有餘。