最稳妥的观察者实现是event+eventhandler;iobservable适用于流式数据;手动实现isubject/iobserver仅限教学或特殊需求。

直接用 event + EventHandler<t></t> 是最稳妥、最符合 .NET 习惯的观察者实现方式;IObservable<t></t> 更适合流式数据或需要取消/错误传播的场景;手写 ISubject/IObserver 接口只在教学或极端定制需求下才值得考虑。
用 event 声明通知,别暴露 public Action
这是 C# 观察者模式最自然的起点。关键不是“能不能”,而是“怎么写才不踩坑”:
-
event提供了访问控制:外部只能用+=/-=,无法清空整个委托链(比如MyEvent = null;这种破坏性操作被禁止) - 必须用
EventHandler<t></t>而非Action<t></t>:前者带object sender参数,能区分多个发布源;后者没有sender,后期加日志、调试、多实例隔离时会卡死 - 事件参数应封装为继承
EventArgs的类,而不是传object或拼接字符串——类型安全、可扩展、IDE 支持好
示例:
public class DataPublisher
{
public event EventHandler<datachangedeventargs> DataChanged;
<pre class="brush:php;toolbar:false;">public void Notify(string value)
{
DataChanged?.Invoke(this, new DataChangedEventArgs(value));
}
}
public class DataChangedEventArgs : EventArgs { public string Value { get; } public DataChangedEventArgs(string value) => Value = value; }
IObservable 不是“更高级”,而是“更特定”
它不是 event 的替代品,而是为“数据流”设计的:比如传感器采样、实时日志、WebSocket 消息流。强行把按钮点击、配置变更这类离散事件转成 IObservable 反而增加复杂度。
- 真正适合它的场景:
Observable.Interval()、Observable.FromEventPattern()、配合System.Reactive做过滤/合并/节流 - 桥接已有 event 时,用
Observable.FromEventPattern,别自己写Subscribe内部调+=——前者自动处理线程上下文和异常隔离 - 返回的
IDisposable必须显式Dispose(),否则底层Timer或事件监听不会释放,容易内存泄漏
错误示范(手动桥接):
// ❌ 别这么干:自己管理 += / -=,没处理异常、没保证线程安全
var obs = Observable.Create<int>(o =>
{
button.Click += (s, e) => o.OnNext(1);
return () => button.Click -= (s, e) => { };
});</int>
正确做法:
// ✅ 用 FromEventPattern,它已封装所有细节
var clicks = Observable.FromEventPattern<routedeventargs>(
h => button.Click += h,
h => button.Click -= h);</routedeventargs>
手动维护观察者列表?先问三个问题
除非你明确需要以下能力,否则不要绕过 event 自己搞 List<iobserver></iobserver>:
- 是否要支持运行时暂停通知(
PauseNotifications())? - 是否要按优先级顺序调用观察者?
- 是否要遍历所有观察者检查其状态(比如是否还存活、是否超时未响应)?
如果答案都是“否”,那就别动。一旦决定手动管理:
- 多线程订阅/取消必须加锁,或改用
ConcurrentBag<iobserver></iobserver>;否则可能抛InvalidOperationException:“集合已被修改” - 每个观察者引用要弱引用(
WeakReference<iobserver></iobserver>)或确保及时Detach(),否则造成内存泄漏——尤其在 UI 控件作为观察者时 -
Notify()中调用观察者方法前,必须判空且捕获异常,避免一个观察者崩溃导致整个通知链中断
取消订阅不是可选项,是必做动作
无论用哪种方式,只要观察者生命周期短于被观察者(比如窗体订阅了全局服务事件),就必须显式取消:
- 用
event:在观察者销毁前调publisher.EventName -= handler; - 用
IObservable<t></t>:保存subscription = source.Subscribe(...),之后调subscription.Dispose() - 手动列表:确保
Detach()被调用,且逻辑上不可重入(重复调用不崩溃)
最容易被忽略的是:异步回调中取消订阅的时机。比如在 Task.Run(() => { ... subscription.Dispose(); }) 里取消,可能因调度延迟导致资源已释放却还在发通知——应该在同步上下文中完成取消。










