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

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










