C#的事件机制是一种基于委托的特殊成员,主要用于实现对象之间的消息通知,让发布事件的对象不需要知道接收事件的对象具体是谁,降低代码之间的耦合度。

事件和委托的关系
事件本质上是对委托的封装,委托定义了方法的签名,事件则限制了外部对委托实例的操作权限。委托可以被直接赋值、调用,而事件只能在声明它的类内部触发,外部只能进行订阅和取消订阅操作。
可以把委托理解为方法的容器,事件就是这个容器的对外暴露的受限接口,避免外部随意修改委托的调用列表或者触发委托。
事件机制的核心组成
一个完整的事件机制通常包含四个部分:
- 事件发布者:定义事件、触发事件的对象
- 事件订阅者:注册事件处理逻辑的对象
- 事件参数:传递事件相关信息的自定义类,通常继承自
EventArgs - 事件委托:定义事件处理方法的签名
自定义事件的实现步骤
1. 定义事件参数类
如果需要传递事件相关的数据,需要先定义继承自EventArgs的参数类:
using System;
// 自定义事件参数,携带消息内容
public class MessageEventArgs : EventArgs
{
public string Message { get; set; }
public MessageEventArgs(string message)
{
Message = message;
}
}
2. 定义事件发布者类
发布者类中需要定义委托、基于委托的事件,以及触发事件的方法:
using System;
public class MessagePublisher
{
// 定义事件委托,签名需要和事件处理方法匹配
public delegate void MessageEventHandler(object sender, MessageEventArgs e);
// 定义事件,基于上面的委托
public event MessageEventHandler MessagePublished;
// 触发事件的方法,通常命名为On+事件名,且为protected virtual方便子类重写
protected virtual void OnMessagePublished(MessageEventArgs e)
{
// 检查事件是否有订阅者,避免空引用异常
MessagePublished?.Invoke(this, e);
}
// 发布消息的方法,内部触发事件
public void PublishMessage(string content)
{
Console.WriteLine($"发布者发布消息:{content}");
MessageEventArgs args = new MessageEventArgs(content);
OnMessagePublished(args);
}
}
3. 定义事件订阅者类
订阅者类需要定义和事件委托签名匹配的处理方法:
using System;
public class MessageSubscriber
{
public string Name { get; set; }
public MessageSubscriber(string name)
{
Name = name;
}
// 事件处理方法,签名需要和MessageEventHandler委托一致
public void HandleMessage(object sender, MessageEventArgs e)
{
Console.WriteLine($"{Name} 收到消息:{e.Message}");
}
}
4. 调用实现事件交互
在主程序中完成事件的订阅和触发:
using System;
class Program
{
static void Main(string[] args)
{
// 创建发布者
MessagePublisher publisher = new MessagePublisher();
// 创建两个订阅者
MessageSubscriber subscriber1 = new MessageSubscriber("订阅者A");
MessageSubscriber subscriber2 = new MessageSubscriber("订阅者B");
// 订阅事件,把处理方法注册到事件的调用列表
publisher.MessagePublished += subscriber1.HandleMessage;
publisher.MessagePublished += subscriber2.HandleMessage;
// 发布者发布消息,触发事件
publisher.PublishMessage("今天天气很好");
// 取消订阅者A的订阅
publisher.MessagePublished -= subscriber1.HandleMessage;
Console.WriteLine("取消订阅者A后再次发布消息:");
publisher.PublishMessage("明天要下雨");
}
}
事件使用的注意事项
- 触发事件前一定要检查事件是否为null,避免没有订阅者时触发导致空引用异常,C# 6.0及以上可以使用
?.语法简化判断 - 事件只能在声明它的类内部触发,外部无法直接调用事件,这是事件和委托的核心区别之一
- 事件参数类建议继承自
EventArgs,这是C#的事件设计规范,方便统一事件参数的结构 - 不需要使用事件时,及时取消订阅,避免订阅者对象无法被垃圾回收,导致内存泄漏
内置事件的使用示例
C#的很多内置类都提供了事件,比如按钮的Click事件,下面的示例演示了控制台模拟按钮点击的逻辑:
using System;
// 模拟按钮类
public class MyButton
{
// 定义点击事件,使用内置的EventHandler委托,它默认接收object和EventArgs参数
public event EventHandler Click;
protected virtual void OnClick(EventArgs e)
{
Click?.Invoke(this, e);
}
// 模拟按钮被点击
public void SimulateClick()
{
Console.WriteLine("按钮被点击");
OnClick(EventArgs.Empty);
}
}
class Test
{
static void Main(string[] args)
{
MyButton button = new MyButton();
// 订阅点击事件
button.Click += (sender, e) => Console.WriteLine("按钮点击处理逻辑执行");
// 模拟点击
button.SimulateClick();
}
}