CodeGym /課程 /C# SELF /把 lambda 表達式 ( =>) ...

把 lambda 表達式 ( =>) 當參數傳遞

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

1. 介紹

到目前為止你已經看到:我們呼叫那些接收 lambda 的 LINQ 方法。但 C# 的方法怎麼知道可以傳入一個函式呢?

簡單說,delegate 就像函式的介面:我們描述簽名(參數型別和回傳值),任何方法(或 lambda!)只要符合這個簽名,就能被傳到期待該 delegate 的位置。

還記得你怎麼把字串當作參數傳遞嗎?處理“邏輯”也是差不多,只是參數的類型變成了 delegate。

委派(delegate):白話的基本理論

在 C# 裡,delegate 是描述「具有某種簽名的函式」的型別。

// 一個接受 int 並回傳 bool 的 delegate
public delegate bool IntPredicate(int x);

任何函式,只要簽名相容,都可以賦值給這種型別的變數:

bool IsEven(int n) => n % 2 == 0;

IntPredicate pred = IsEven;

現在 lambda 也可以:

IntPredicate pred = x => x % 2 == 0;

泛用 delegate:Func, Action, Predicate

  • Func<T1, ..., TResult> — 函式,接受參數 T1, ... 並回傳 TResult
  • Action<T1, ...> — 函式,接受參數但不回傳值(void)。
  • Predicate<T> — 函式,接受 T 並回傳 bool

2. 在自己的方法中傳遞 lambda

假設我們在開發教學用的小程式 — 一個操作使用者清單的 console 專案。之前我們用 LINQ 來過濾集合,現在我們來寫一個自家的方法,接收一個 lambda 條件。

建立一個有 lambda 參數的方法

// 為範例定義 User 類別(放到我們的應用裡)
public class User
{
    public string Name { get; set; }
    public bool IsActive { get; set; }
}

// 接受使用者清單和 delegate 條件(lambda)的 方法
public static List<User> FilterUsers(List<User> users, Predicate<User> predicate)
{
    var result = new List<User>();
    foreach (var user in users)
    {
        if (predicate(user)) // 呼叫 lambda!
            result.Add(user);
    }
    return result;
}

現在可以傳任何 lambda:

var users = new List<User>
{
    new User { Name = "瓦西亞", IsActive = true },
    new User { Name = "佩佳", IsActive = false },
    new User { Name = "瑪莎", IsActive = true }
};

// 只篩選啟用的使用者
var activeUsers = FilterUsers(users, user => user.IsActive);

foreach (var user in activeUsers)
    Console.WriteLine(user.Name); // 瓦西亞, 瑪莎

就這樣!我們把一段邏輯 — 小函式 — 作為普通參數傳入,因為方法 FilterUsers 等待的是 Predicate<User>,而我們給了它相容的 lambda。

使用 Func<T, TResult> 的變體

Predicate<T> 適合用在需要條件(回傳 bool)的場景。若是想對每個使用者「計算」些什麼,該用 Func<T, TResult>

// 對每個元素套用函式並收集結果的 方法
public static List<TResult> MapUsers<TResult>(List<User> users, Func<User, TResult> selector)
{
    var result = new List<TResult>();
    foreach (var user in users)
    {
        result.Add(selector(user));
    }
    return result;
}

使用範例:

var names = MapUsers(users, user => user.Name.ToUpper());

foreach (var name in names)
    Console.WriteLine(name); // 瓦西亞, 佩佳, 瑪莎

3. 有用的注意事項

不同的傳遞形式

你不只能傳 lambda,還可以傳一般的方法 — 重點是簽名要吻合。

// 一般方法
static bool NameHasS(User user) => user.Name.Contains("s");

// 傳一般方法:
var usersWithS = FilterUsers(users, NameHasS);
// 傳 lambda
var usersWithA = FilterUsers(users, u => u.Name.Contains("a"));

也可以用舊式匿名方法(不推薦):

var usersWithM = FilterUsers(users, delegate(User u) { return u.Name.Contains("m"); });

現代風格是用 lambda!

把 lambda 傳給 LINQ:實際發生了什麼

var result = users.Where(u => u.IsActive).ToList();

底層上 Where 接受的是 Func<User, bool>。這代表任何接受 Func<...> 的方法,都可以用同樣的方式呼叫!

如果想要兩個參數怎麼辦?

// 方法接受兩個 lambda 來做過濾
public static List<User> FilterUsersCustom(
    List<User> users,
    Func<User, bool> include,
    Func<User, bool> exclude)
{
    var result = new List<User>();
    foreach (var user in users)
    {
        if (include(user) && !exclude(user))
            result.Add(user);
    }
    return result;
}

使用範例:

var customFiltered = FilterUsersCustom(
    users,
    u => u.Name.StartsWith("V"),
    u => u.IsActive == false
);
// 只會取名字以 "V" 開頭且為啟用的使用者

情境:過濾器工廠

Console.WriteLine("請輸入名字的最小長度:");
int minLength = int.Parse(Console.ReadLine());

Predicate<User> lengthFilter = user => user.Name.Length >= minLength;
var filteredUsers = FilterUsers(users, lengthFilter);

// 相當互動又有趣!

4. 典型錯誤和注意點

有時候編譯器無法正確「推斷」lambda 的參數型別 — 特別是在有 overloads(多載)或方法需要特定參數數量/回傳型別的 delegate 時。這種情況可以顯式標註 lambda 的型別:

FilterUsers(users, (User u) => u.Name.Length > 3);

或是:

MapUsers(users, (User u) => u.Name.ToUpper());

錯誤:lambda 與簽名不匹配

FilterUsers(users, user => Console.WriteLine(user.Name)); // 錯誤! 預期 bool,得到 void

因為預期的是回傳 bool 的函式,而這個 lambda 回傳的是 void(實際上沒有明確回傳)。注意回傳型別很重要!

錯誤:濫用 lambda

如果你的 lambda 寫到 10 行,那就把它抽出成一個獨立的方法。這樣比較易讀,也比較好除錯。

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION