CodeGym /课程 /C# SELF /把 lambda 表达式 ( =>) ...

把 lambda 表达式 ( =>) 当作参数传递

C# SELF
第 50 级 , 课程 2
可用

1. 介绍

到目前为止你已经看到:我们调用过一些 LINQ 的方法,它们接受 lambda 作为参数。但 C# 方法怎么知道“可以传入一个函数”呢?

简单来说,委托(delegate)就像函数的接口:我们定义签名(参数类型和返回值),任何符合这个签名的方法(包括 lambda!)都可以传到期待该委托的地方。

还记得你怎么把字符串作为参数传吗?把“逻辑”当参数也是类似的,只不过参数类型是委托。

委托:通俗易懂的基础理论

在 C# 里,委托是描述“具有某种签名的函数”的类型。

// 一个接收 int 并返回 bool 的委托
public delegate bool IntPredicate(int x);

任何与签名兼容的函数都可以赋值给这个类型的变量:

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

IntPredicate pred = IsEven;

现在 —— lambda 也可以:

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

通用委托: Func, Action, Predicate

  • Func<T1, ..., TResult> — 一个接收参数 T1, ... 并返回 TResult 的函数。
  • Action<T1, ...> — 一个接收参数但不返回值(void)的函数。
  • Predicate<T> — 一个接收 T 并返回 bool 的函数。

2. 把 lambda 传到自定义方法

假设我们在开发一个教学用的小项目 —— 控制台程序,操作一个用户列表。之前我们通过 LINQ 过滤集合,这次写一个自己的方法,接受一个 lambda 条件。

创建一个带 lambda 参数的方法

// 定义示例用的 User 类(加入到我们的应用里)
public class User
{
    public string Name { get; set; }
    public bool IsActive { get; set; }
}

// 接收列表和委托条件(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)的时候。如果想对每个用户做点“计算”,怎么办?

// 对每个元素应用函数并收集结果的方法
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 参数的类型 —— 特别是在重载复杂、或者方法需要特定返回类型/多个参数的委托时。此时可以显式写出 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 行代码,最好把它提取到单独的方法里。这样更易读,也更方便调试。

2
任务
C# SELF, 第 50 级, 课程 2
已锁定
复杂的用户过滤和转换
复杂的用户过滤和转换
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION