适用场景:GUI交互、消息队列、微服务通信等需要解耦事件生产与消费的系统
🧩 模式核心组件解析
发布者(Publisher)
- 作用:定义事件并管理订阅者列表
- 关键行为:
- 提供+=和-=运算符注册/注销订阅者
- 通过Invoke()方法触发事件 (图示15-3)
- 代码示例:
public class Incrementer {public event EventHandler CountedADozen; // 定义事件 public void DoCounting() {for (int i=1; i<=100; i++) {if (i % 12 == 0) CountedADozen?.Invoke(this, EventArgs.Empty); // 触发事件 }}
}
订阅者(Subscriber)
- 职责:向发布者注册事件处理方法
- 设计原则:
- 方法需匹配委托签名(参数+返回值)
- 避免长时间阻塞(影响其他订阅者)
注册示例
public class Dozens {public void Subscribe(Incrementer publisher) {publisher.CountedADozen += OnCountedDozen; // 注册事件 }private void OnCountedDozen(object sender, EventArgs e) {Console.WriteLine("12的倍数已累计!");}
}
事件与委托的共生关系(关键!)
- 事件本质:对委托的封装(图示15-2)
- 封装价值
- 安全优势:阻止外部重置订阅列表,保障事件触发的确定性
⚙️ 实战设计要点
事件参数自定义
public class CountEventArgs : EventArgs {public int CurrentCount { get; set; } // 传递计数状态
}
// 发布者触发时传递参数
CountedADozen?.Invoke(this, new CountEventArgs { CurrentCount = i });
多线程环境安全
// 使用原子操作避免线程竞争
EventHandler handler = CountedADozen;
if (handler != null) {handler(this, EventArgs.Empty);
}
解耦进阶方案
❗ 避坑指南
内存泄漏风险
- 问题:订阅者未注销 → 阻止GC回收对象
- 方案:在Dispose()中执行publisher.Event -= handler
事件处理异常传播
- 陷阱:某个订阅者抛异常 → 中断后续执行
- 方案:独立捕获每个处理器的异常
foreach (var handler in CountedADozen.GetInvocationList()) {try { handler.DynamicInvoke(args); } catch (Exception ex) { /* 记录日志 */ }
}
技术启示:事件驱动架构通过解耦生产者与消费者,显著提升系统扩展性。掌握其底层委托机制,可避免“仅会用不懂原理”的窘境。建议结合Rx.NET或MediatR库深化实战能力。