在 C# 中,观察者模式可以通过 IObservable 和 IObserver 接口来实现。请解释这两个接口各自承担的角色,并说明一个典型的实现步骤:包括定义观察者、订阅机制、通知流程以及如何取消订阅。同时,请指出使用这两个接口与直接自定义委托或事件方式相比,在异步推送或多线程场景下的优势或注意事项。
考察说明
考查对 C# 中 IObservable/IObserver 接口的理解及其在观察者模式中的具体实现与特性。
回答思路
- 【回答框架 1】IObservable<T> 和 IObserver<T> 是 .NET 标准接口,用于提供基于推送的通知机制。IObservable<T> 代表可观察对象,其核心方法是 Subscribe(IObserver<T> observer),返回 IDisposable 用于取消订阅;IObserver<T> 代表观察者,包含 OnNext、OnError 和 OnCompleted 三个方法,分别处理数据推送、异常和通知结束。
- 【回答框架 2】实现步骤:首先定义具体观察者类实现 IObserver<T>,在 OnNext 中处理数据,OnError 中处理异常,OnCompleted 中做清理;然后定义可观察类实现 IObservable<T>,内部维护订阅者列表,Subscribe 方法需要将观察者加入列表并返回一个 IDisposable,其 Dispose 方法用于从列表移除;最后在数据产生时遍历列表调用各观察者的 OnNext。
- 【回答框架 3】相比使用事件或委托,IObservable/IObserver 更规范,且天然支持异步推送(如在 Task 中生产数据)和错误处理(通过 OnError 传递异常)。但需要注意线程安全:多线程环境下的订阅、通知和取消订阅可能产生竞态条件,通常需要锁或使用同步集合。
- 【回答框架 4】此外,IObservable 接口本身不提供内置的线程模型,实现时需自行保证线程安全,例如在通知时对订阅者列表加锁。也可借助 System.Reactive(Rx.NET)库中的实现,它提供了丰富的 LINQ 操作符来组合和处理数据流,但需注意该库并非 .NET 标准库的一部分。
- 【回答框架 5】关键点:订阅方法的返回值必须实现 IDisposable,且 Dispose 需是幂等的;在通知过程中如果有观察者取消订阅,应避免修改正在遍历的集合。
- 【关键点 1】IObservable<T> 是数据源,IObserver<T> 是订阅者,Subscribe 返回 IDisposable 用于取消订阅。
- 【关键点 2】Observe 模式支持推送通知,通过 OnNext 推送数据,OnError 传递异常,OnCompleted 通知结束。
- 【关键点 3】实现时需处理线程安全问题,可使用锁或同步集合保证订阅和通知的原子性。
- 【关键点 4】取消订阅应在 Dispose 中实现,且需避免在遍历时修改订阅列表。
- 【易错点 1】若在 OnNext 中调用观察者的取消订阅方法,可能引发集合修改异常,应使用迭代器副本或延迟移除。
- 【易错点 2】IObservable 接口不保证线程安全,未加锁时多线程同时订阅或通知会导致数据竞争。
- 【易错点 3】误以为 Subscribe 方法必须立即传递所有已有数据,实际上默认是推送后续新数据,除非结合其他机制(如 ReplaySubject)。