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。
GO TO FULL VERSION