1. 介紹
你知道嗎?Lambda 表達式本身就像一個沒鍋子的食譜。它描述了要做什麼,但需要一個「容器」才能在程式碼裡存在。C# 有現成的通用委派類型可以當這個容器:Func、Action 和 Predicate。
把它們想像成給你 Lambda 的模具——挑對的模子把你的邏輯倒進去就行。別再自幹自造委派類型了,90% 情況下已有標準解。
小小歷史
一開始,當需要把函式作為參數傳遞時,你得自己聲明委派類型。那既耗時又麻煩,像這樣:
delegate int Calculate(int x, int y);
Calculate adder = (a, b) => a + b;
當 C# 引入了 Func, Action 和 Predicate,你就可以在大多數情況下忘掉手動定義委派了。現在看起來簡潔又通用:
Func<int, int, int> adder = (a, b) => a + b;
2. Func<T, TResult> — 有回傳值的函數
語法與用途
Func 是一個泛型委派(generic delegate),它可以接受從 0 到 16 個參數,並且會回傳一個值。
Func<int, int, int> sum = (x, y) => x + y;
Func<型別1, 型別2, ..., 型別N, TResult> — 除了最後一個以外的所有型別參數都是輸入參數,最後一個是回傳型別。
範例
1. 兩數相加
Func<int, int, int> sum = (x, y) => x + y;
Console.WriteLine(sum(3, 5)); // 8
2. 平方
Func<int, int> square = x => x * x;
Console.WriteLine(square(4)); // 16
3. 無參數
Func<string> greet = () => "嗨,Lambda!";
Console.WriteLine(greet());
視覺示意
| 簽名 | 範例 | 說明 |
|---|---|---|
|
|
接受 int,返回 int |
|
|
兩個 int,返回 int |
|
|
不接受任何參數,返回字串 |
在你的應用中長什麼樣
假設在我們的小應用(假想的「使用者名錄」)裡,有一個整數清單 — 想對它做不同處理。例如,把每個數平方或跟一個固定數相加:
List<int> numbers = new() { 1, 2, 3, 4, 5 };
Func<int, int> square = x => x * x;
var squares = numbers.Select(square);
Console.WriteLine(string.Join(", ", squares)); // 1, 4, 9, 16, 25
3. Action<T> — 沒回傳值的動作
Action 是用來表示那些做事情(比如印到螢幕),但不回傳任何值的方法的通用委派。
它可以接受從 0 到 16 個參數,但一定會回傳 void。
範例
1. 印到螢幕
Action<string> print = text => Console.WriteLine("資料: " + text);
print("嗨,世界!");
2. 無參數的動作
Action greet = () => Console.WriteLine("歡迎!");
greet();
3. 多參數的動作
Action<int, int> showSum = (a, b) => Console.WriteLine($"總和: {a + b}");
showSum(2, 3); // 總和: 5
視覺示意
| 簽名 | 範例 | 說明 |
|---|---|---|
|
|
無參數、無回傳 |
|
|
一個參數 |
|
|
多個參數 |
在我們的應用中
在使用者名錄中加入一個列印所有名字的方法:
List<string> names = new() { "安娜", "鮑里斯", "維卡" };
Action<string> printName = name => Console.WriteLine("使用者: " + name);
names.ForEach(printName);
// 或者這樣: names.ForEach(name => Console.WriteLine(name));
4. Predicate<T> — 是或不是?
當你需要一個函數,對某個參數只回傳 true 或 false,就用 Predicate<T>。它就是一個接受一個參數並回傳 bool 的委派。
Predicate — 官方上說就是「我們需要一個布林檢查」的包裝器。
範例
1. 檢查是否大於 5
Predicate<int> isGreaterThanFive = x => x > 5;
Console.WriteLine(isGreaterThanFive(3)); // false
Console.WriteLine(isGreaterThanFive(7)); // true
2. 與方法 List<T>.Find 的搭配
List<int> values = new() { 2, 4, 7, 10 };
int found = values.Find(isGreaterThanFive); // 使用 Predicate<int>
Console.WriteLine(found); // 7
3. 都是成年人嗎?
List<int> ages = new() { 12, 19, 34 };
bool allAdults = ages.TrueForAll(age => age >= 18);
// TrueForAll 接受 Predicate<int>
和 Func<T, bool> 的差別
實際上,它們在很多情況下可以互換。就連 Microsoft 文件也說:"Predicate<T> 只是針對特定 API 的 Func<T, bool>"。但是標準函式庫有時會明確要求 Predicate。
5. Lambda 怎麼「套」進 Func, Action, Predicate
當你寫一個 lambda,C# 會分析:「喔,這個形式剛好符合要求的委派 —— 可以直接代入!」
Func<int, int> f1 = x => x * 2;
Action<string> a1 = text => Console.WriteLine(text);
Predicate<int> p1 = x => x < 10;
到處都是 lambda!但底層是三個不同簽名的委派。
在「真實」程式碼的應用
List<User> users = new() {
new User("安娜", 24),
new User("鮑里斯", 17),
new User("維卡", 31),
};
// 函數:只回傳成年使用者 (Predicate<User>)
List<User> adults = users.FindAll(user => user.Age >= 18);
Console.WriteLine("成年名單: " + string.Join(", ", adults.Select(u => u.Name)));
如果想用 Action<User> 印出所有使用者名字:
users.ForEach(user => Console.WriteLine(user.Name));
取得他們的名字(Func<User, string>):
IEnumerable<string> names = users.Select(user => user.Name);
對照表(方便記憶)
| 委派 | 簽名 | Lambda 範例 | 哪裡用到 |
|---|---|---|---|
|
T → U | |
Select、各種轉換 |
|
T → void | |
ForEach、執行動作的方法 |
|
T → bool | |
Find, Exists, 過濾 |
6. 內建應用範例:一步步來
我們來擴充小應用「使用者名錄」。假設有一個 User 類別:
public class User
{
public string Name { get; }
public int Age { get; }
public bool IsActive { get; set; }
public User(string name, int age)
{
Name = name;
Age = age;
IsActive = true;
}
}
1. Func<User, bool> — 檢查使用者是否成年
Func<User, bool> isAdult = user => user.Age >= 18;
在 LINQ 中使用:
var adults = users.Where(isAdult);
2. Predicate<User> — 找到一個啟用中的使用者
Predicate<User> isActive = user => user.IsActive;
User found = users.Find(isActive);
3. Action<User> — 將使用者設為非啟用
Action<User> deactivate = user => user.IsActive = false;
users.ForEach(deactivate);
4. Func<User, string> — 取得簡短描述
Func<User, string> describe = user => $"{user.Name} ({user.Age})";
var descriptions = users.Select(describe);
這些 lambda 本質上就是「以資料形式存在的程式碼」,可以傳給方法、存在變數裡、或組合起來使用。
7. 不那麼明顯的細節與常見錯誤
1. Lambda 必須符合委派簽名。 如果簽名不符,會編譯錯誤。
Func<int, string> wrong = x => x * 2; // 錯誤: 預期 string,但得到 int
// 正確寫法:
Func<int, string> right = x => (x * 2).ToString();
2. 別忘了 void 跟 return 的差別。 Action 不會回傳值——像是 Action<int> a = x => x * x; 這種會回傳值的寫法會失敗,因為不該有回傳值。
3. Predicate<T> 與 Func<T, bool> 常可互換,但不是每次都行。 有時集合的方法會明確要求 Predicate<T>,有時要 Func<T, bool>。直接賦值不一定能通過編譯,可能要顯式包裝。
Predicate<int> pred = x => x > 0;
Func<int, bool> func = pred; // 錯誤
// 但可以:
Func<int, bool> func2 = x => x > 0;
Predicate<int> pred2 = new Predicate<int>(func2); // 可以用建構子轉換
GO TO FULL VERSION