1. Introduction
想象你有台咖啡机。通常它只是煮咖啡,不会干别的,但有时候老板会说:「你能在煮完咖啡后喊一声“好了!”吗?」——这就需要配置。我们不改咖啡机的代码,只是创建一个函数,让它在最后执行。
在 C# 里,这个角色由 委托 承担 —— 它们允许把一段逻辑(方法、lambda 或匿名方法)传给另一个方法,以便在合适的时候调用。简单说,委托是一种可以保存指向具有特定签名的方法引用的类型。
Delegate definition
在 C# 中,委托用关键字 delegate 来定义。示例:
// 委托,引用接受 int 并返回 bool 的方法
public delegate bool PredicateInt(int x);
现在类型为 PredicateInt 的变量可以引用任何接受一个 int 并返回 bool 的方法(或者 lambda!)。
What are delegates for?
- 把逻辑作为参数传递(比如排序、过滤、事件处理)
- 订阅事件(后面会讲)
- 实现回调(callback)
- 设计灵活的 API,让调用方决定部分行为
Simple visual schema
| 委托类型 | 签名 | 调用示例 |
|---|---|---|
|
|
|
|
|
|
|
|
|
2. How a lambda turns into a delegate
Синтаксис
当你写一个 lambda,比如 x => x > 5,本质上是在创建一个委托对象。lambda 并不是孤立存在的:它必须有一个类型(某处需要知道它的参数和返回类型)。这就是为什么 C# 中的 lambda 总是被隐式或显式地转换成某个委托类型。
Example 1: Binding a delegate to a method
// 明确定义委托
public delegate bool MyPredicate(int number);
class Program
{
static void Main()
{
// 把 lambda 赋给 MyPredicate 类型的变量
MyPredicate isEven = x => x % 2 == 0;
Console.WriteLine(isEven(4)); // true
Console.WriteLine(isEven(7)); // false
}
}
Example 2: Using standard delegates
C# 提供了一组通用的标准委托:Action、Func<>、Predicate<>。在写 lambda 的地方它们经常出现。
// 使用 Func
Func
isPositive = number => number > 0; Console.WriteLine(isPositive(-5)); // false
3. Standard delegates: Func, Action, Predicate
Func<...>
用于那些有输入并且有返回值的方法。
签名说明:
— 最后一个类型参数是返回值,之前的都是参数类型,例如:
Func<int, string> — 接受一个 int,返回一个 string
Func
intToString = number => "数字: " + number; Console.WriteLine(intToString(7)); // "数字: 7"
Action<...>
用于不需要返回值的方法(void)。
Action
printHello = name => Console.WriteLine("你好," + name + "!"); printHello("瓦西里"); // "你好,瓦西里!"
Predicate<T>
本质上是 Func<T, bool> 的简化版。用于对对象做布尔判断(true/false)。
Predicate
isOdd = x => x % 2 != 0; Console.WriteLine(isOdd(3)); // true
Visual cheat sheet
| 委托 | 签名 | 用途 |
|---|---|---|
|
|
转换、映射 |
|
|
副作用、输出 |
|
|
过滤、查找 |
Which delegate type to choose for a lambda expression?
- 如果有返回值,选 Func<...>
- 如果方法不返回值(void),用 Action<...>
- 如果需要做布尔判断,选 Predicate<T>
Example: List filtering
List
numbers = new List
{ 1, 2, 3, 4, 5, 6 }; // 该方法期望 Predicate
List
evenNumbers = numbers.FindAll(x => x % 2 == 0); Console.WriteLine(string.Join(", ", evenNumbers)); // 2, 4, 6
4. Useful nuances
Lambda expressions and collection methods: what's under the hood?
当你在调用集合方法时传入 lambda,例如:
var adults = users.Where(u => u.Age >= 18);
方法 Where 期望一个 Func<T, bool> 类型的参数。也就是说,你的 lambda u => u.Age >= 18 被编译器转换成这个类型的委托对象。
Flowchart: how it works
你的 lambda 表达式 --> C# Compiler --> 委托对象 (Func
) (u => u.Age >= 18) [类型已知] (准备好在 Where() 中调用)
More details on typing: type inference
通常情况下,如果上下文足够清楚,编译器会自动推断出委托类型。例如,对 List<T>.Find 来说,会期望 Predicate<T>,编译器能从方法签名里知道参数类型。
List
words = new List
{ "one", "two", "three" }; var result = words.Find(word => word.Length == 5); // Find 会期望 Predicate
Console.WriteLine(result); // "three"
如果上下文不明确,就需要帮编译器指定类型:
// 显式指定类型
Func
check = x => x > 10;
Returning delegates: function factory
有时候方法会返回委托 —— 也就是生成“函数工厂”。这在需要动态生成行为时很方便。
// 返回委托(lambda)的函数
Func
GetMultiplier(int factor) { return x => x * factor; } var times3 = GetMultiplier(3); Console.WriteLine(times3(5)); // 15
这是可行的,因为 lambda (x => x * factor) 捕获了外部上下文中的变量 factor(closure/闭包),并作为 Func<int, int> 类型的对象返回。
5. Errors and misunderstandings with delegates and lambdas
Signature mismatch
如果参数或返回类型不匹配,编译器不会允许把 lambda 赋给委托。
Func
f = x => "不能返回字符串!"; // 编译错误
Error when trying to use a lambda without a delegate
不能直接写一个 lambda 然后尝试调用它而不给出类型:
// 这样不行 —— 编译器无法推断类型
// var myFunc = x => x * 2; // Error CS0815
// myFunc(10);
要让它工作,需要显式指定类型,或者提供上下文:
Func
myFunc = x => x * 2; Console.WriteLine(myFunc(10)); // 20
Confusion between Action, Func and Predicate
有时会选错委托类型,导致签名不匹配。记住简单规则:Func —— 有返回值时用,Action —— 没有返回值(void)时用,Predicate<T> —— 需要布尔结果(bool)时用。
GO TO FULL VERSION