当在针对.NET 4+的库中公开通知时,是否应优先选择IObservable而不是事件?

30
我有一个.NET库,作为对象模型的一部分,会发出某些事件的通知。
对于初学者来说,事件的主要优点是易于接近(以及在某些消费情况下简单),主要缺点是它们不可组合,因此如果您想做任何有趣的事情而不编写代码块,则立即被迫进入Observable.FromEvent*。
解决问题的性质使得事件流量不会特别频繁或大量(它绝对不会呼喊RX),但绝对没有要求支持.NET 4.0之前的版本[因此我可以在API设计中使用内置的IObservable界面,在System.Reactive上,而不会对消费者造成任何重大依赖]。无论我的具体情况位于event-IObservable光谱的哪个位置,我都对从API设计角度更喜欢IObservables而不是events的一些具体理由感兴趣。
所以,问题是:

如果我选择最简单的方法并公开一个event而不是IObservable,是否会让API消费者面临更多困难或问题?

换句话说:除了消费者必须执行Observable.FromEvent*以组合事件之外,在API中公开通知时,是否真的没有任何理由优先选择IObservable而不是event

最好提供使用IObservable进行非RX相关操作或编码指南的项目引用,但这不是关键。


NB 正如 @Adam Houldsworth 在评论中提到的那样,我对 .NET 4+ 库的 API 表面上的具体事物感兴趣,而不是关于哪种架构更适合我们时代的观点调查 :)

NB 这个问题在 C# 中的 IObserver 和 IObservable 用于 Observer vs Delegates、EventsIObservable vs Plain Events 或者为什么我应该使用 IObservable? 中已经被提到。我所询问的问题在所有回答中都没有得到解决,因为涉及 SRP 违规。另一个略微重叠的问题是 .NET Rx 相对于经典事件的优势是什么?使用 IObservable 而不是 events 属于同一类别。


对我来说,这个问题与RX无关 - System.ReactiveIObservable是.NET的标准部分,我不会考虑强制消费者依赖于RX。 (对我来说,这有点受到F#标准库对消费IObservable的良好库支持的影响) - Ruben Bartelink
@AdamHouldsworth 是的,但 RX 团队为了某个原因而费尽心思将 System.Reactive [和 IObservable] 加入核心。正如我所引用的那样,F# 有很好的消费支持,其他语言也可以提供类似的支持。虽然你说得对,RX 是一个不可忽视的问题,但组合事件的方法不止一种(考虑 F# 代理...)。顺便说一句,我并不是在争论你的中心论点,即这是一个意见领域,但我绝对不想要一个程序员.SE的答案。 - Ruben Bartelink
这个问题可能值得一看:https://dev59.com/Pmgu5IYBdhLWcg3wnoaC - Adam Houldsworth
没问题。我从客观的角度看待它,意识到评论本身可能并没有太多价值,但对问题的修改是有用的。我仍在阅读这个话题,因为我觉得它非常有趣。 - Adam Houldsworth
@AdamHouldsworth 虽然这是一个有趣的问题,但对我来说只是一个架构问题。删除了第一条评论;在您确认后将删除此评论。 - Ruben Bartelink
4个回答

15
在这个答案的评论中,OP进一步澄清了他的问题:
确实每一个事件都可以被适配到一个IObservable上吗?
对于这个问题,基本上是肯定的,但有一些注意事项。同时需要注意的是,反过来不成立 - 请参阅有关反向转换以及结论部分,了解为什么基于附加含义,Observables可能比经典事件更受欢迎。
对于严格的翻译,我们只需将事件(应包括发送者以及参数)映射到OnNext调用上即可。Observable.FromEventPattern帮助方法很好地完成了这项工作,重载返回IObservable<EventPattern<T>>,提供了发送对象和EventArgs
注意事项
回想一下Rx语法。这可以表示为EBNF格式: Observable Stream = { OnNext }, [ OnError | OnCompleted ] - 或0个或多个OnNext事件,后跟任意一个OnCompleted或OnError。
这背后隐含的是从单个订阅者的角度来看,事件不会重叠的想法。要明确的是,这意味着订阅者不会同时被调用。此外,其他订阅者可能会在不同的时间以及同时被调用。通常情况下,正是订阅者自己通过以比到达速度更慢的速度处理事件(创建反压)来控制事件流的节奏。在这种情况下,典型的Rx运算符针对单个订阅者排队,而不是阻塞整个订阅者池。相比之下,经典的.NET事件源更倾向于按步骤广播给订阅者,等待所有订阅者完全处理一个事件,然后再继续。这是经典事件的长期假定行为,但实际上并没有规定。 C# 5.0语言规范(这是一个Word文档,请参见1.6.7.4节)和.NET Framework设计指南:事件设计 对事件行为的说明非常少。该规范观察到:
“提出事件的概念与调用表示事件的委托是完全等价的 - 因此,没有特殊的语言构造用于引发事件。”C# Programming Guide : Events 部分表示:
当事件有多个订阅者时,事件处理程序在触发事件时同步调用。要异步调用事件,请参见 调用同步方法异步。
因此,传统的事件通常通过在单个线程上调用委托链来顺序发布,但在指南中没有这样的限制 - 偶尔我们会看到并行调用委托的情况 - 但即使在这种情况下,事件的两个实例通常也会依次被触发,即使每个实例都是并行广播的。
我无法在官方规范中找到明确说明事件实例本身必须按顺序被触发或接收的内容。换句话说,没有任何内容表明无法同时触发多个事件实例。
这与 observables 相反,在Rx 设计指南中明确说明了以下内容:
4.2. 假设观察者实例以串行方式调用
请注意,此语句仅涉及单个订阅者实例的视点,不涉及跨实例并发发送事件的问题(实际上在 Rx 中非常普遍)。
因此,以下是两个要点:
- 虽然 OnNext 概念上表示事件,但经典的 .NET 事件可能会通过并发调用事件而违反 Rx 语法。 - 在负载下,纯 Rx Observables 具有不同的事件传递语义是很常见的,因为反压通常基于每个订阅者而不是每个事件进行处理。
只要您的 API 不会同时引发事件,并且您不关心反压语义,那么通过类似于 Rx 的 Observable.FromEvent 机制的转换到 observable 接口就没问题了。

反向转换

请注意,在经典的 .NET 事件中,OnError 和 OnCompleted 没有类似物,因此没有办法进行反向映射而不需要一些额外的机制和约定使用方式。
例如,可以将 OnError 和 OnCompleted 转换为其他事件 - 但这绝对超出了经典事件范畴。此外,需要在不同处理程序之间使用一些非常麻烦的同步机制;在 Rx 中,一个订阅者可以在另一个订阅者仍在接收 OnNext 事件时收到 OnCompleted - 在经典的 .NET 事件转换中更难实现这一点。

错误

考虑错误情况下的行为:重要的是要区分事件源中的错误和处理程序/订阅者中的错误。 OnError 用于处理前者,在后者的情况下,经典事件和 Rx 都会(并且正确地)崩溃。这方面的错误在任何方向上都不易传递。

结论

.NET 经典事件和 Observables 不是同构的。只要遵循正常的使用模式,从事件转换到 Observable 可以相对容易地完成。也许您的 API 需要 observables 的附加语义,而这些语义无法轻松地用 .NET 事件建模,因此更有意义的是仅使用 Observable - 但这是一种具体考虑而非一般性考虑,并且更多是设计问题而不是技术问题。

作为一般指导,如果可能的话,我建议优先选择经典事件,因为它们被广泛理解和支持,并且可以进行转换 - 但是,如果您需要表示源错误或完成的额外语义,则毫不犹豫地使用 observables,这些额外语义以 OnError 和 OnCompleted 事件的优雅形式呈现。


3

在开始涉足 Reactive extensions 之前,我阅读了很多相关内容。经过一些困难的开端后,我发现它们非常有趣和实用。

Observables 扩展提供了一个可选参数,你可以传递你自己的时间管理器,有效地让你操作时间。在我的情况下,它帮助我很多,因为我正在执行一些与时间相关的工作(每十分钟检查这个 Web 服务,每分钟发送一封电子邮件等),并且它使得测试变得轻而易举。我会将 TestScheduler 插入要测试的组件中,并在瞬间模拟一天的事件。

因此,如果你的库中有一些工作流需要时间协调,我真的建议使用 Observables 作为你的输出。

但是,如果你只是针对用户输入直接触发事件,我认为增加复杂性对于用户来说并不值得。正如你所指出的,他们可以在需要时将事件包装成自己的 Observable。

你可以两全其美,虽然这意味着更多的工作;通过从你的事件创建观察者序列,提供一个门面,将你的事件库转换为基于 Observable 的库。或者反过来:提供一个可选门面,当触发时,订阅你的 Observable 并引发一个经典事件。

在我看来,在处理响应式扩展时需要采取一个不平凡的技术步骤,而在这种情况下,它可能取决于你的 API 消费者最喜欢使用什么。


抱歉,和其他答案一样,显然我需要更改问题 - 我想确认人们是否能够从“事件”中进行适应,或者在某些情况下它实际上是必须首先成为“IObservable”? - Ruben Bartelink
现在已经对问题进行了大量编辑-很抱歉原始版本缺乏清晰度(尽管您的答案显然很好,但我无法将其视为我的问题的答案。如果它被重新发布为其中一个答案,我会非常高兴给予赞同) - Ruben Bartelink

1
< p > IObservable 是事件的 IEnumerable,所以唯一的问题是你认为 IObservable 是否会成为标准,就像现在的 IEnumerable 一样,所以是的,它更可取,如果你认为它只是一个过渡性的东西,将来会被淘汰,那么请使用 event

IObservable 在大多数情况下比 event 更好,但我个人认为我会忘记使用它,因为它不太常用,当需要使用时我可能已经忘记了它。

我知道我的回答并没有什么帮助,但只有时间才能告诉我们 RX 是否会成为标准,我认为它有很好的机会。

[编辑] 使其更具体。

对于最终用户,唯一的区别是一个是接口,另一个不是,使接口更易于测试和扩展,因为不同的来源可以实现相同的接口。

正如Adam Houldsworth所说,可以轻松地将一个更改为另一个,以使其没有其他区别。


IObservable 在 .NET Core 中已经被广泛应用于平台绑定。但是 event 也是如此。正如我在与 @AdamHouldsworth 的评论讨论中提到的那样,这确实与 RX 无关。我的问题是关于我现在在 API 中要做什么 - 出于具体原因,而不是任何未来证明或架构宇航员的基础。 - Ruben Bartelink
2
@RubenBartelink 具体原因只能根据消费者的需求来实现。我认为重要的是保持一致性。最终,如果您要引发事件并且有人想要一个可观察对象,他们会围绕它进行操作并继续前进。反之亦然,如果给定一个可观察对象并且想要一个事件,也是如此。我个人认为两者的公共契约足够相似,不需要在意,这取决于消费方式。而且,由于我没有将Rx视为您问题的关键点,所以这种讨论是没有价值的。 - Adam Houldsworth
1
@AdamHouldsworth,正如我对Pedro所说的那样,我只是想知道是否有具体的原因。你提到的一致性点绝对很关键。事实上,我倾向于基于KISS的event。我正在寻找一些能够让我相信除了人们必须为真正的原因适应event之外,公开IObservable的单一好处。 - Ruben Bartelink
更明确的处理和订阅模式怎么样?IObservable 更明确地是一种设计方法,鼓励一定的编码风格和对订阅者和对象生命周期的意识。这可能会对潜在的内存泄漏产生更积极的影响,通常是由于事件订阅的意识和管理不足而实现的。我个人倾向于反应式编程(如果可以的话,因为我们的遗留应用程序很难做到),它带来了一组开发模式和实践,提供了自己的优势。然而,在没有已知需求的情况下,KISS 胜利。 - Adam Houldsworth
问题已经被大幅修改。希望能够删除或修订答案,因为现在情况有些混乱了。 - Ruben Bartelink
显示剩余2条评论

1

回答您的标题问题:

不应只因为它们存在于.NET 4中并可供使用而偏爱,偏好取决于预期使用,因此一个全面的偏好是不合理的。

话虽如此,我倾向于将其作为传统C#事件的替代模型。

正如我在整个问题中所评论的那样,采用IObservable接口有许多附加优点,其中最重要的是外部支持和最终消费者的选择范围。

回答您的内部问题:

我认为,在API中公开事件或IObservable都没有太大困难,因为在两种情况下都可以从一种转到另一种。这会在您的API上增加一层,但实际上这是您也可以发布的一层。

我认为,选择其中之一不会成为某人选择使用或不使用您的API的决定原因。

回答您重新表述的问题:

可能的原因可以在为什么首先有一个Observable.FromEvent找到 :-) IObservable 在 .NET 中的许多地方都得到了支持,用于响应式编程,并成为许多流行库(Rx、Ix、ReactiveUI)的一部分,也与LINQ和IEnumerable很好地互操作,并进一步扩展到TPL和TPL DataFlow等内容。
非 Rx 示例中的可观察模式,因此不是特定的IObservable,对于 XAML 应用程序来说,是ObservableCollection

再次感谢您的输入和努力,但对我来说,关键在于是否存在任何好处,如果您使用event而不是使用IObservable,那么消费者使用Observable.FromEvent*是否会带来更大的痛苦。 - Ruben Bartelink

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接