应优先用event处理轻量通知,仅在需流式组合、变换或错误恢复时选iobservable;二者不可混用,桥接须用observable.fromeventpattern且注意生命周期管理。

直接说结论:在 C# 里,IObservable<t></t> 和事件(event)是两种不同层级的发布-订阅机制,不是“怎么用它们一起实现观察者模式”的问题,而是“该选哪个、怎么用对、别混用”的问题。
什么时候该用 event,而不是 IObservable<t></t>
绝大多数业务场景下,你该用 event:UI 按钮点击、属性变更通知(INotifyPropertyChanged)、文件系统变化(FileSystemWatcher.Changed)……这些都基于 event,因为它们天然符合“一对多、无状态、不关心订阅者生命周期”的轻量通知需求。
常见错误现象:IObservable<t></t> 被当成“高级版 event”硬套进 WinForms/WPF 事件处理逻辑里,结果引入不必要的引用计数、订阅泄漏、冷热源混淆等问题。
-
event是语言级语法糖,编译后就是委托字段 +add/remove方法,开销极低 -
event不提供取消订阅以外的控制能力(比如重试、缓冲、转换),这反而是优势——边界清晰 - 如果发布者生命周期短于订阅者(如临时 ViewModel 订阅了长期存活的服务),用
event更安全(没IDisposable需求)
什么时候必须用 IObservable<t></t>
当你需要组合、变换、节流、重试、错误恢复、或统一管理多个异步数据流时,IObservable<t></t> 才真正有用。典型场景是 Rx.NET(Reactive Extensions)驱动的数据管道,比如实时行情聚合、传感器数据滤波、用户操作行为流分析。
使用场景示例:从多个 IObservable<int></int> 源合并出一个去重+限频的点击流,再投递给后台服务。
-
IObservable<t></t>的核心契约是Subscribe(IObserver<t>)</t>,返回IDisposable,意味着你必须显式管理订阅生命周期 - 它默认是“冷”的(每次
Subscribe都触发新执行),和event的“热”广播本质不同;误以为“订阅就自动收到历史事件”是高频踩坑点 - 不要手动实现
IObservable<t></t>—— 用Observable.Create或Subject<t></t>,否则极易写出线程不安全或资源泄漏的代码
event 和 IObservable<t></t> 怎么桥接(真要混用时)
只有当已有成熟 event 接口(如第三方库)但下游要用 Rx 处理时,才需要桥接。别为了“看起来更函数式”而强行转。
正确做法是用 Observable.FromEventPattern 封装,而不是手写 Subscribe 内部调 +=。
var clicks = Observable.FromEventPattern<mousebuttoneventargs>(
h => button.Click += h,
h => button.Click -= h
);</mousebuttoneventargs>
- 桥接后得到的是热 observable,但原始
event本身仍存在,别忘了原始事件监听器还在工作 - 桥接产生的
IDisposable只负责解绑这个 Rx 订阅,不影响其他event订阅者 - 避免用
Subject<t></t>做双向桥接(比如把event往里推、又从Subject订阅出去)—— 这会绕过event的线程安全保证,且让调用栈不可追溯
性能与兼容性现实约束
.NET 6+ 默认支持 IObservable<t></t>,但它的实际采用率远低于 event。很多基础类库(包括 System.Threading.Channels、Microsoft.Extensions.Hosting.IHostedService)压根不提供 observable 接口。
-
event在 .NET Framework / .NET Core / .NET 5+ 全平台一致,零兼容性风险 -
IObservable<t></t>在 AOT 编译(如 .NET MAUI iOS)中可能因反射被裁剪,需额外保留策略 - Rx.NET 包(
System.Reactive)体积约 1.2MB,对小型工具类项目可能是过度依赖
最常被忽略的一点:观察者模式的关键从来不是接口形态,而是谁持有发布者引用、谁负责清理订阅。用 event 容易漏掉 -=,用 IObservable<t></t> 容易忘记 Dispose —— 两者泄漏表现不同,但根源一样:生命周期管理没对齐。










