C#中应使用event关键字声明委托实现观察者模式,避免直接暴露公共委托字段;推荐EventHandler而非Action以符合.NET约定并支持sender参数;需精细控制时可手动管理观察者列表但须注意线程安全与内存泄漏。

用 event 声明委托是标准做法
在 C# 中,观察者模式最自然、最符合语言习惯的实现方式就是使用 event 关键字。它本质是对委托的封装,提供访问控制(外部只能 += / -=,不能直接赋值或调用),避免观察者意外干扰发布者内部逻辑。
常见错误是直接暴露公共委托字段,比如:public Action——这会让订阅者能清空整个委托链(OnDataChanged = null;),破坏观察者机制。
正确写法是:
public class DataPublisher
{
// 声明事件:基于 EventHandler 或自定义委托
public event EventHandler DataChanged;
public void Notify(string value)
{
// 线程安全检查(.NET 6+ 可用 null-forgiving,但推荐显式判空)
DataChanged?.Invoke(this, value);
}}
EventHandler 比原生 Action 更合适
虽然可以用 public event Action DataChanged; ,但不推荐。原因有三:
-
EventHandler 是 .NET 标准约定,第一个参数固定为 object sender,便于观察者识别事件来源;
- 它天然支持 WinForms/WPF/ASP.NET 等框架的事件系统,后续扩展更平滑;
- 当需要取消订阅或区分多个发布者时,
sender 是唯一可靠依据,而 Action 没有该信息。
若需传递多个参数,不要拼接字符串或用 object[],应定义专用事件参数类:
public class DataChangedEventArgs : EventArgs
{
public string Value { get; }
public DateTime Timestamp { get; }
public bool IsCritical { get; }
public DataChangedEventArgs(string value, bool isCritical = false)
{
Value = value;
Timestamp = DateTime.UtcNow;
IsCritical = isCritical;
}}
// 使用方式
public event EventHandler DataChanged;
手动管理观察者列表适合需要精细控制的场景
当标准 event 不够用时——比如要支持优先级订阅、条件过滤、运行时暂停通知、或需遍历所有观察者做状态检查——就得绕过 event,自己维护 List> 或 Dictionary> 。
这时要注意:
- 必须加锁(如
lock(_lock))或用线程安全集合(ConcurrentBag> ),否则多线程订阅/取消会引发 InvalidOperationException;
- 取消订阅不能只靠
list.Remove(handler),因为委托相等性判断复杂,建议用唯一 ID 或 WeakReference 避免内存泄漏;
-
Invoke 时若某个观察者抛异常,默认会中断后续调用,需用 try/catch 包裹单个处理逻辑。
别忽略事件生命周期与内存泄漏
最常见的坑是:观察者(尤其是 UI 控件或长期存活对象)订阅了事件,但没在销毁时取消订阅,导致发布者无法被 GC 回收。
典型表现:
- 窗体关闭后仍收到通知;
- 性能分析器显示某类实例数持续增长;
- 调试时发现
publisher.DataChanged 的委托链里还挂着已释放对象的方法。
解决方法很简单,但必须成对出现:
// 订阅
publisher.DataChanged += OnPublisherDataChanged;
// 取消(例如在 Form.Closing、IDisposable.Dispose、或 ViewModel 的 Cleanup 中)
publisher.DataChanged -= OnPublisherDataChanged;
如果使用匿名函数或 lambda,就无法干净取消——所以生产代码中,事件处理必须是命名方法。










