CSharp委托学习笔记
一、什么是委托
委托(Delegate)是 C# 中一种类型安全的函数指针。它可以把方法当作参数进行传递,或者将方法赋值给变量。
委托可以理解为一个方法的容器:定义委托变量,将符合条件的方法“装”进去,然后通过委托变量调用方法。
类比
委托就像一个遥控器:遥控器本身不是设备,但可以控制设备。
- 定义委托类型 = 设计遥控器
- 实例化委托 = 绑定具体设备
- 调用委托 = 按下按钮执行操作
为什么需要委托
- 解耦:调用方不直接依赖具体方法
- 回调:将方法作为参数传递
- 事件驱动:事件基于委托实现
- 灵活性:运行时动态决定调用哪个方法
二、委托的声明
使用 delegate 关键字声明委托类型,语法类似方法定义但没有方法体。
// 无参数、返回 void 的委托
public delegate void MyDelegate();
// 两个 int 参数、返回 int 的委托
public delegate int CalculateDelegate(int x, int y);
委托声明定义的是一个类型,之后可以用它创建变量。
三、实例化委托并使用
创建委托实例时需要传入符合签名的方法,可以使用 new 关键字或直接赋值方法名。
static int Add(int a, int b) => a + b;
static int Multiply(int a, int b) => a * b;
CalculateDelegate calc = new CalculateDelegate(Add);
// 简写:CalculateDelegate calc = Add;
int result = calc(3, 5); // 8
Console.WriteLine(result);
calc = Multiply;
Console.WriteLine(calc(3, 5)); // 15
委托调用方式:
calc(3, 5)直接调用calc.Invoke(3, 5)使用Invoke方法
四、多播委托
一个委托实例可以绑定多个方法,调用时按添加顺序依次执行。使用 += 添加,-= 移除。
public delegate void NotifyDelegate(string message);
static void SendEmail(string msg) => Console.WriteLine($"邮件通知: {msg}");
static void SendSMS(string msg) => Console.WriteLine($"短信通知: {msg}");
static void SendLog(string msg) => Console.WriteLine($"日志记录: {msg}");
NotifyDelegate notify = SendEmail;
notify += SendSMS;
notify += SendLog;
notify("系统更新完成");
// 输出:
// 邮件通知: 系统更新完成
// 短信通知: 系统更新完成
// 日志记录: 系统更新完成
notify -= SendSMS;
notify("系统再次更新");
// 输出:
// 邮件通知: 系统再次更新
// 日志记录: 系统再次更新
注意:多播委托通常返回 void。若有返回值,多播时只返回最后一个方法的返回值。
五、内置泛型委托:Func 与 Action
.NET 提供常用的泛型委托,大多数情况无需自定义委托。
1. Action —— 无返回值
表示没有返回值的方法,可接受 0 到 16 个参数。
Action sayHello = () => Console.WriteLine("Hello!");
sayHello();
Action<string, int> showInfo = (name, age) =>
{
Console.WriteLine($"姓名: {name}, 年龄: {age}");
};
showInfo("小明", 20);
2. Func —— 有返回值
最后一个类型参数是返回值类型,前面的是参数类型。
Func<int> getRandom = () => new Random().Next(1, 100);
Console.WriteLine(getRandom());
Func<int, int, int> add = (a, b) => a + b;
Console.WriteLine(add(3, 7)); // 10
Func<string, bool> isNullOrEmpty = s => string.IsNullOrEmpty(s);
Console.WriteLine(isNullOrEmpty("test")); // False
3. Predicate —— 返回 bool 的委托
接受一个参数并返回 bool,常用于筛选条件。
Predicate<int> isPositive = x => x > 0;
Console.WriteLine(isPositive(5)); // True
Console.WriteLine(isPositive(-3)); // False
Predicate<T> 与 Func<T, bool> 功能相同。
六、匿名方法与 Lambda 表达式
匿名方法(C# 2.0)
CalculateDelegate calc = delegate(int a, int b)
{
return a - b;
};
Console.WriteLine(calc(10, 4)); // 6
Lambda 表达式(C# 3.0+)
CalculateDelegate calc2 = (a, b) => a - b;
Console.WriteLine(calc2(10, 4)); // 6
Action<string> print = msg => Console.WriteLine($"消息: {msg}");
print("Hello Lambda");
Func<string> getTime = () => DateTime.Now.ToLongTimeString();
Console.WriteLine(getTime());
现代 C# 推荐使用 Lambda 表达式创建委托实例。
七、委托与事件的区别
事件是委托的特殊用法,封装了委托,限制外部代码直接调用或清空委托链。
| 特性 | 委托 | 事件 |
|---|---|---|
| 声明方式 | delegate 关键字 |
event 关键字 + 委托类型 |
| 外部调用 | 可以直接调用 | 只能在声明事件的类内部调用 |
| 外部赋值 | 可以使用 = 覆盖 |
只能使用 += 或 -= |
| 用途 | 回调、方法传递 | 发布-订阅模式 |
public delegate void EventHandler(string message);
public event EventHandler OnMessageReceived;
// 触发事件(类内部)
OnMessageReceived?.Invoke("有新消息");
使用事件而非公开委托是为了防止外部随意赋值或清空委托链,提供更好的封装性。
八、总结
- 委托是方法的类型,可以把方法赋值给委托变量。
- 使用
delegate声明委托类型,+=/-=实现多播。 Action用于无返回值方法,Func用于有返回值方法。- Lambda 表达式是创建委托实例的简洁方式。
- 事件基于委托,用于实现安全的发布-订阅模式。
一句话记忆:委托 = 方法的“变量”,可以把方法当参数传,也可以把多个方法串在一起调用。
九、相关面试题
1. 什么是委托?它和函数指针有什么区别?
委托是 C# 中类型安全的函数指针,可以封装一个或多个方法,并支持多播。与 C/C++ 函数指针相比,委托是类型安全的、面向对象的,且支持多播和异步调用。
2. 委托和事件有什么区别?
事件是基于委托的封装,外部只能订阅/退订(+=/-=),不能直接调用或赋值,提高了安全性。委托可以被外部直接调用和覆盖。
3. 什么是多播委托?如何实现?
多播委托是一个委托实例可以绑定多个方法,调用时按添加顺序依次执行。使用 += 添加方法,-= 移除方法。通常用于通知、回调等场景。
4. Action 和 Func 的区别是什么?
Action 表示无返回值的方法,可接受 0 到 16 个参数。Func 表示有返回值的方法,最后一个类型参数为返回值类型,可接受 0 到 16 个输入参数。
5. 什么是匿名方法和 Lambda 表达式?它们与委托的关系?
匿名方法是 C# 2.0 引入的简化委托实例化的语法,Lambda 表达式是 C# 3.0 引入的更简洁写法。它们都是创建委托实例的便捷方式,本质上都会生成一个委托对象。
6. 委托可以指向静态方法吗?实例方法呢?
可以。委托既可以指向静态方法,也可以指向实例方法。指向实例方法时,委托会持有对象引用,调用时自动绑定到该实例。
7. 为什么委托比直接调用方法更灵活?
委托将方法的调用与实现解耦,允许在运行时动态指定要调用的方法,支持回调、事件、插件式架构等场景,提高代码的可扩展性和可维护性。
8. 什么是闭包?Lambda 表达式中的闭包有什么陷阱?
闭包是指 Lambda 表达式或匿名方法捕获了外部变量。陷阱:循环中捕获循环变量时,可能导致所有委托共享同一个变量值。C# 5.0 后 foreach 循环变量每次迭代都会创建新变量,但 for 循环仍需注意。
9. 委托的 Invoke 和 BeginInvoke 有什么区别?
Invoke 是同步调用,会阻塞当前线程直到方法执行完成。BeginInvoke 是异步调用,在后台线程执行方法,通过 IAsyncResult 获取结果。现代 .NET 推荐使用 Task 和 async/await 替代 BeginInvoke。
10. 如何自定义一个带返回值的多播委托?调用时返回值是什么?
多播委托可以有返回值,但调用时只返回最后一个方法的返回值,前面的返回值会被丢弃。若需要获取所有返回值,可以使用 GetInvocationList() 逐个调用。

浙公网安备 33010602011771号