CodeGym /課程 /C# SELF /把 delegate 當作 lambda 表達式 的類型

把 delegate 當作 lambda 表達式 的類型

C# SELF
等級 50, 課堂 0
開放

1. 介紹

想像你有一台咖啡機。通常它只負責煮咖啡,不會去做別的事,但有時主管會說:「你能不能在煮完咖啡後大喊『完成了!』?」— 這時就需要一個設定。我們不會重寫咖啡機本身,只是建立一個函式,讓它在結尾時呼叫。

在 C# 裡,這個角色就是 delegate — 它讓你可以把程式邏輯(方法、lambda 或匿名方法)傳進另一個方法,讓那個方法在需要的時候呼叫它。簡單講,delegate 是一種型別,可以儲存指向符合特定簽名的方法的引用。

Delegate 的定義

在 C# 中,delegate 是用關鍵字 delegate 來定義。範例:

// Delegate,會參考那些接受 int 並回傳 bool 的方法
public delegate bool PredicateInt(int x);

現在類型為 PredicateInt 的變數可以引用任何接受一個 int 並回傳 bool 的方法(或 lambda)。

delegate 有什麼用?

  • 把邏輯以參數的形式傳遞(例如用在排序、過濾、處理事件)
  • 訂閱事件(這部分後面會講)
  • 實作回呼(callback)
  • 建立彈性 API,讓呼叫者決定部分行為

簡單的視覺示意

Delegate 類型 簽名 呼叫範例
Action
void()
Action act = () => ...
Func<int>
int()
Func<int> f = () => 42
Func<int, bool>
bool(int)
Func<int, bool> p = x => x > 0

2. lambda 怎麼變成 delegate

語法

當你寫一個 lambda 表達式,例如 x => x > 5,本質上你是在建立一個 delegate 物件。lambda 不會「孤立存在」:它一定需要一個型別(要有人知道參數和回傳值長什麼樣)。所以在 C# 裡,lambda 表達式總是會被隱含地(或明確地)轉換成某個 delegate。

範例 1:把 delegate 綁到方法

// 明確定義 delegate 類型
public delegate bool MyPredicate(int number);

class Program
{
    static void Main()
    {
        // 把 lambda 指派給 MyPredicate 類型的變數
        MyPredicate isEven = x => x % 2 == 0;

        Console.WriteLine(isEven(4));  // true
        Console.WriteLine(isEven(7));  // false
    }
}

範例 2:使用內建的 delegate

C# 內建了一組泛型 delegate:Action、Func<>、Predicate<>。幾乎在你寫 lambda 的地方都會看到它們。

// 使用 Func<int, bool>
Func<int, bool> isPositive = number => number > 0;
Console.WriteLine(isPositive(-5)); // false

3. 內建 delegate:Func, Action, Predicate

Func<...>

用於那些會接受參數並回傳值的方法。

簽名:
— 最後一個型別是回傳值,之前的都是參數型別,例如:
Func<int, string> — 接受 int,回傳 string

Func<int, string> intToString = number => "數字: " + number;
Console.WriteLine(intToString(7)); // "數字: 7"

Action<...>

用在只要執行某事但不需回傳(void)的情況。

Action<string> printHello = name => Console.WriteLine("嗨, " + name + "!");
printHello("瓦西里"); // "嗨, 瓦西里!"

Predicate<T>

本質上是 Func<T, bool> 的簡寫。用在需要對物件做布林檢查(true/false)的場景。

Predicate<int> isOdd = x => x % 2 != 0;
Console.WriteLine(isOdd(3)); // true

快速參考

Delegate 簽名 用途
Func<T1, TResult>
Result(T1)
轉換、投影
Action<T1>
void(T1)
副作用、輸出
Predicate<T>
bool(T)
過濾、搜尋

要為 lambda 選哪個 delegate?

  • 如果預期有回傳值,就用 Func<...>
  • 如果方法不回傳任何東西(void),用 Action<...>
  • 如果需要做條件檢查,使用 Predicate<T>

範例:過濾清單

List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6 };

// FindAll 期望的是 Predicate<int>
List<int> evenNumbers = numbers.FindAll(x => x % 2 == 0);
Console.WriteLine(string.Join(", ", evenNumbers)); // 2, 4, 6

4. 一些實用細節

lambda 與集合方法:底層發生了什麼?

當你呼叫集合方法並傳入 lambda,例如:

var adults = users.Where(u => u.Age >= 18);

方法 Where 期望的參數型別是 Func<T, bool>。也就是說,你的 lambda u => u.Age >= 18 會被編譯器轉成該型別的 delegate 物件。

流程圖:運作方式


你的 lambda         -->      編譯器 C#        -->     delegate 物件 (Func<T, bool>)
(u => u.Age >= 18)          [型別已知]                (準備好可在 Where() 中被呼叫)

再說型別:型別推導

通常編譯器會自動推導出 delegate 的型別,只要上下文清楚。例如對於方法 List<T>.Find,它會期望 Predicate<T>,而編譯器會從方法簽名知道參數的型別。

List<string> words = new List<string> { "one", "two", "three" };
var result = words.Find(word => word.Length == 5); // Find 期望 Predicate<string>
Console.WriteLine(result); // "three"

如果上下文不明確,你就得幫編譯器一把:

// 明確指定型別
Func<int, bool> check = x => x > 10;

回傳 delegate:函數工廠

有時候方法會回傳 delegate,也就是建立「函數工廠」。這對於產生動態行為很方便。

// 回傳 delegate(lambda)的函數
Func<int, int> GetMultiplier(int factor)
{
    return x => x * factor;
}

var times3 = GetMultiplier(3);
Console.WriteLine(times3(5)); // 15

這能工作的原因是 lambda (x => x * factor) 捕獲了外部的變數 factor(closure/閉包),並且被當作 Func<int, int> 型別的物件回傳。

5. 常見錯誤與誤解(delegate 與 lambda)

簽名不匹配

如果參數或回傳型別不一致,編譯器不會允許把 lambda 指派給該 delegate。

Func<int, int> f = x => "不能回傳字串!"; // 編譯錯誤

嘗試在沒有 delegate 的情況下使用 lambda 時的錯誤

你不能直接寫一個 lambda 然後嘗試呼叫它而不給它一個型別:

// 這樣不會成功 - 編譯器無法推導型別
// var myFunc = x => x * 2; // 錯誤 CS0815 
// myFunc(10);

要讓它工作,你需要明確指定型別或提供上下文:

Func<int, int> myFunc = x => x * 2;
Console.WriteLine(myFunc(10)); // 20

混淆 Action、Func 和 Predicate

有時候會不小心選錯 delegate 類型,導致簽名不匹配。記住簡單規則:Func — 有回傳值時用,Action — 沒有回傳值(void)時用,Predicate<T> — 需要布林判斷(bool)時用。

2
任務
C# SELF, 等級 50, 課堂 0
上鎖
建立並使用委派來處理 lambda 運算式
建立並使用委派來處理 lambda 運算式
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION