CodeGym /课程 /C# SELF /结合 lambda 表达式和 delegate

结合 lambda 表达式和 delegate

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

1. 引言

Delegate 是一种类型,用来描述“函数签名”:它接受哪些参数,返回什么。它就像函数的护照或夜总会的通行证:签名匹配就能进去,不匹配就别想了。

示例:

public delegate int Operation(int a, int b);

这个 delegate 表示一个接受两个 int 参数并返回 int 的函数。那时的“非 lambda”编程(新鲜,但不太方便)长这样:

Operation add = delegate (int x, int y)
{
    return x + y;
};

随着 lambda 的出现,代码变得更简洁:

Operation add = (x, y) => x + y;

有趣的事实:在 C# 中,lambda 表达式其实就是创建“匿名函数”的方式,它会自动编译成符合类型的 delegate 实例。而它们之间的关系,几乎变得难以区分!

内置的 delegate:Func、Action、Predicate

为了避免为每个场景都写自己的 delegate,C# 提供了一些“通用”的函数类型:

  • Func<T1, T2, ..., TResult> — 通用 delegate,接受参数 (T1, T2, ...),返回 TResult 类型的值。
  • Action<T1, T2, ...> — 类似,但没有返回值(void)。
  • Predicate<T> — 接受一个参数,返回 bool(所有“过滤器”的宠儿)。
Delegate 返回类型 示例语法
Func<int, int> int Func<int, int> f = x => x+1;
Action<string> void Action<string> a = s => ...;
Predicate<double> bool Predicate<double> p = d => ...;

示例:

Func<int, int, int> add = (a, b) => a + b;
Action<string> show = s => Console.WriteLine(s);
Predicate<int> isEven = n => n % 2 == 0;

2. Lambda 表达式“封装”成 delegate

将 lambda 传递给带 delegate 的方法

任何接受 delegate 的方法,都可以“吃掉” lambda,只要签名匹配。

public static void CalculateAndShow(int a, int b, Func<int, int, int> operation)
{
    int result = operation(a, b);
    Console.WriteLine($"结果: {result}");
}

// 调用时传入 lambda
CalculateAndShow(5, 7, (x, y) => x * y); // 结果: 35

示例:Action

void ProcessString(string message, Action<string> processor)
{
    processor(message);
}

// 使用 lambda
ProcessString("你好,世界!", s => Console.WriteLine(s.ToUpper()));

示例:Predicate

List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
List<int> evenNumbers = numbers.FindAll(n => n % 2 == 0);

在这些场景中,你会发现:不用自己写 delegate 类型,微软的“轮子”已经帮你做好了。

delegate 和 lambda 如何协作

      flowchart TD A[Lambda 表达式] -->|创建| B((Delegate)) B -->|作为参数传递| C[方法] B -->|存储| D[属性/字段/集合] C -->|调用| E[Lambda 代码] 
    

3. 将 lambda 作为 delegate 传入自定义方法

如果你的方法用 delegate,lambda 让调用变得极其灵活。

示例:迷你计算器

在我们的学习应用(比如一个控制台工具,用于测试函数)中,加入“传递逻辑”的支持:

// 定义处理操作的方法
static int PerformOperation(int a, int b, Func<int, int, int> operation)
{
    return operation(a, b);
}

// 使用不同的操作(lambda 在发挥作用!)
int sum = PerformOperation(3, 4, (x, y) => x + y);
int multiply = PerformOperation(3, 4, (x, y) => x * y);
int max = PerformOperation(3, 4, (x, y) => x > y ? x : y);

Console.WriteLine(sum);      // 7
Console.WriteLine(multiply); // 12
Console.WriteLine(max);      // 4

看出来没有?逻辑变得多优雅,原来要写三个不同方法的地方,现在只用一个!

4. LINQ 中 lambda + delegate(不仅如此)

在 LINQ 中,方法都期待“逻辑小函数”,也就是 delegate。几乎所有标准 LINQ 方法都接受这些类型:

  • Func<T, bool> 用于过滤(Where
  • Func<T, TResult> 用于变换(Select
  • Func<T, TKey> 用于排序(OrderBy
List<string> names = new List<string> { "Anna", "Oliver", "Victor", "Yana" };

// 获取名字长度大于3的
var longNames = names.Where(name => name.Length > 3);

// 转成大写
var upperNames = names.Select(name => name.ToUpper());

LINQ 就是“lambda 的极致”,因为所有可以用 delegate 表达的,都可以用 lambda 简洁写出来!

5. 组合:存储和传递 lambda delegate

函数列表

Lambda 不只是临时的匿名方法,你可以把它们存到集合里,动态调用。

List<Func<int, int, int>> operations = new List<Func<int, int, int>>
{
    (x, y) => x + y,
    (x, y) => x - y,
    (x, y) => x * y,
    (x, y) => x / y
};

foreach (var op in operations)
{
    Console.WriteLine(op(10, 2));
}

这种技巧在测试、事件处理和插件中很常见。

delegate 字典

如果你喜欢 plug-and-play 架构:

var mathFuncs = new Dictionary<string, Func<int, int, int>>
{
    { "plus", (x, y) => x + y },
    { "minus", (x, y) => x - y },
    { "pow", (x, y) => (int)Math.Pow(x, y) }
};

string command = "pow"; // 模拟用户输入

if (mathFuncs.TryGetValue(command, out var operation))
{
    Console.WriteLine(operation(2, 5)); // 32
}
else
{
    Console.WriteLine("命令未找到");
}

6. 细节和注意事项

lambda delegate:从方法返回 delegate

可以从方法返回 delegate(也就是 lambda),几乎变成了函数工厂!

Func<int, int> GetMultiplier(int factor)
{
    // 返回一个 lambda delegate,用来乘以 factor
    return x => x * factor;
}

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

var quadruple = GetMultiplier(4);
Console.WriteLine(quadruple(5)); // 20

这样,闭包(closure)就不再是玩具,而是真正的工具!

函数组合:Combine、Delegate.Combine 和多重 delegate

有时候希望一个“调用”能同时执行多个函数,比如 GUI 事件处理:点按钮——多个处理器同时响应。

Action 和 delegate 允许“拼接”多个函数:

Action<string> pipeline = s => Console.WriteLine("步骤 1: " + s);
pipeline += s => Console.WriteLine("步骤 2: " + s.ToUpper());
pipeline += s => Console.WriteLine("步骤 3: " + s.Length);

pipeline("测试");
// 输出:
// 步骤 1: 测试
// 步骤 2: 测试
// 步骤 3: 4

适合“链式”场景或事件处理。

注意:对于有返回值的函数,结果只来自“最后一次调用”(“尾部函数”)。而对于 Action,所有都执行,因为返回类型是 void

用 lambda delegate 作为 callback 参数

经典场景——“等你干完再叫我”。

void DownloadFile(string url, Action<string> onFinish)
{
    // 模拟下载
    System.Threading.Thread.Sleep(500);
    onFinish($"下载 '{url}' 完成!");
}

DownloadFile("http://example.com", msg => Console.WriteLine(msg));

在实际应用中(比如 API、数据库、文件操作),这是标准的设计模式。

高阶函数:示例过滤器(Predicate)

lambda 和 delegate 让你写出通用函数,接受其他函数作为参数。这就是“高阶函数”模板的基础。

List<int> Filter(List<int> source, Predicate<int> condition)
{
    List<int> result = new List<int>();
    foreach (var item in source)
        if (condition(item)) result.Add(item);
    return result;
}

// 使用不同的 lambda
var onlyPositive = Filter(new List<int> { -2, 0, 2, 7 }, x => x > 0);
var onlyEven = Filter(new List<int> { -2, 0, 2, 7 }, x => x % 2 == 0);

甚至可以写“工厂”函数,生成不同的过滤条件:

Func<int, Predicate<int>> GetRangeChecker(int min, int max) =>
    x => x >= min && x <= max;

Predicate<int> inRange = GetRangeChecker(1, 10);

var filtered = Filter(new List<int> { 0, 5, 10, 15 }, inRange); // 5, 10

7. 常见错误和注意事项

当 lambda 和 delegate 组合时,有一些细节要注意:

  • 如果参数签名不匹配,编译器会报错(“CS1660: Cannot convert lambda expression”)。
  • lambda 可能“记住”后续会变化的变量(闭包——hello,bug!)。
  • 带返回值的多重 delegate,只会返回“最后的结果”,这对新手来说也是个坑。
  • 某些场合需要表达式(Expression<Func<int,bool>>),不能用语句 lambda,只能用表达式 lambda。
2
任务
C# SELF, 第 50 级, 课程 4
已锁定
使用 Func 和 Action
使用 Func 和 Action
1
调查/小测验
委托 - 适用于 lambda 表达式的类型第 50 级,课程 4
不可用
委托 - 适用于 lambda 表达式的类型
lambda 表达式和委托的关系
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION