在C#的开发体系中,委托(delegate)是一种类型安全的函数指针,它可以指向具有相同签名的方法,而事件(event)是基于委托封装的、用于实现发布订阅模式的特殊成员,二者常搭配使用实现模块间的松耦合通信。

一、委托的定义与使用
1. 委托的定义语法
委托需要先定义类型,再声明实例,定义时需要指定返回值类型和参数列表,只有签名匹配的方法才能赋值给该委托实例。
// 定义无返回值、带一个string参数的委托类型 public delegate void MessageHandler(string message);
2. 委托的实例化与绑定方法
委托实例可以直接赋值方法,也可以通过+=运算符绑定多个方法,调用时所有绑定的方法会按顺序执行。
class Program
{
// 定义和委托签名匹配的方法
static void PrintMessage(string msg)
{
Console.WriteLine($"打印消息:{msg}");
}
static void SaveMessage(string msg)
{
Console.WriteLine($"保存消息:{msg}");
}
static void Main()
{
// 实例化委托,直接赋值第一个方法
MessageHandler handler = PrintMessage;
// 绑定第二个方法
handler += SaveMessage;
// 调用委托,两个方法都会执行
handler("测试委托调用");
}
}
3. 委托的触发
委托实例可以直接像方法一样调用,调用前建议先判断是否为空,避免出现空引用异常。
static void Main()
{
MessageHandler handler = PrintMessage;
handler += SaveMessage;
// 触发前判断是否为空
if (handler != null)
{
handler("触发委托");
}
}
二、事件的定义与触发
1. 事件的定义语法
事件必须基于已经定义的委托类型声明,声明时需要在委托类型前加event关键字,事件只能在声明它的类内部触发,外部只能订阅或取消订阅。
// 定义发布者类
public class MessagePublisher
{
// 定义委托类型
public delegate void MessagePublishHandler(string content);
// 基于委托定义事件
public event MessagePublishHandler OnMessagePublished;
// 触发事件的方法,只能在类内部调用
public void Publish(string content)
{
Console.WriteLine($"发布者发布消息:{content}");
// 触发事件前判断是否有订阅者
if (OnMessagePublished != null)
{
OnMessagePublished(content);
}
}
}
2. 事件的订阅与取消订阅
外部类只能通过+=订阅事件,通过-=取消订阅,不能直接调用事件或者给事件赋值。
// 定义订阅者类
public class MessageSubscriber
{
public void ReceiveMessage(string content)
{
Console.WriteLine($"订阅者收到消息:{content}");
}
}
class Program
{
static void Main()
{
MessagePublisher publisher = new MessagePublisher();
MessageSubscriber subscriber = new MessageSubscriber();
// 订阅事件
publisher.OnMessagePublished += subscriber.ReceiveMessage;
// 触发发布者的发布方法,间接触发事件
publisher.Publish("第一条消息");
// 取消订阅
publisher.OnMessagePublished -= subscriber.ReceiveMessage;
publisher.Publish("第二条消息");
}
}
三、delegate和event的核心区别
很多开发者会疑惑为什么有了委托还要有事件,二者的核心差异主要体现在使用限制和访问权限上,具体对比如下:
| 对比维度 | delegate(委托实例) | event(事件) |
|---|---|---|
| 声明位置限制 | 可以在类的任何地方声明,也可以作为局部变量 | 只能作为类的成员,不能作为局部变量 |
| 外部访问权限 | 外部可以直接调用、直接赋值(覆盖原有绑定) | 外部只能通过+=订阅、-=取消订阅,不能直接调用或直接赋值 |
| 封装性 | 封装性弱,外部可以随意修改委托的绑定状态 | 封装性强,外部无法干预事件的触发和绑定逻辑,只能被动订阅 |
| 使用场景 | 适合作为回调参数传递,或者需要外部直接控制调用逻辑的场景 | 适合实现发布订阅模式,需要限制外部只能订阅不能主动触发的场景 |
四、使用注意事项
- 委托和事件触发前都必须判断是否为空,避免空引用异常,尤其是事件可能在没有任何订阅者时为null。
- 事件只能在声明它的类内部触发,即使在派生类中也不能直接触发基类的事件,需要在基类中提供受保护的触发方法。
- 如果一个委托实例需要被多个外部模块订阅,且不需要外部主动触发,优先使用事件,提升代码的安全性和封装性。
- 长时间持有委托或事件的引用可能导致对象无法被垃圾回收,不再需要时及时取消订阅。
委托是事件的基础,事件是对委托的封装,二者配合可以实现灵活的消息通知机制,是C#中解耦模块的重要方式。