草庐IT

c# - 在面向 .NET 4+ 的库中公开通知时,IObservable 是否应该优先于事件

coder 2023-07-11 原文

我有一个 .NET 库,作为对象模型的一部分,它会发出某些事件的通知。

在我看来 的主要优点事件 对于初学者来说是平易近人的(并且在某些消费环境中很简单),主要的负面影响是它们not composable因此立即被迫进入 Observable.FromEvent * 如果您想在不编写代码丛林的情况下做任何有趣的事情。

正在解决的问题的性质是,事件流量不会特别频繁或大量(绝对不是尖叫 RX),但绝对不需要支持 4.0 之前的 .NET 版本[因此我可以使用内置IObservable接口(interface)在 System.Reactive不强制对消费者产生任何重大依赖]。我对一些一般准则感兴趣,一些特定的具体原因更喜欢 IObservablesevent不过,从 API 设计的角度来看 - 无论我的具体案例可能位于 event 的哪个位置- IObservable光谱。

所以,问题:

如果我采用最简单的方法并公开 event,那么对于 API 使用者来说,有什么具体的事情我会变得更加困难或有问题吗?而不是 IObservable
或者,重申:除了消费者必须做一个 Observable.FromEvent * 为了能够组合事件,真的没有一个理由喜欢 IObservable超过 event在 API 中公开通知时?

使用项目的引用 IObservable对于不尖叫 RX 的东西或编码指南将是理想的,但并不重要。

NB 在@Adam Houldsworth 的评论中提到,我对 .NET 4+ 库的 API 表面的具体事物感兴趣,而不是关于哪个代表我们时代更好的“默认架构”的意见调查: )

注意此问题已在 IObserver and IObservable in C# for Observer vs Delegates, Events 中提及和 IObservable vs Plain Events or Why Should I use IObservable? .由于违反了 SRP,我所提出的问题的任何方面都没有在任何回复中得到解决。另一个稍微重叠的问题是 Advantages of .NET Rx over classic events? . (使用 IObservable 而不是事件)[ Use of IObservable instead of events属于同一类别。

最佳答案

在这个答案的评论中,OP 将他的问题提炼为:

[Is it] indeed definitely the case that each and every event can always be Adapted to be an 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 Language Specification (这是一个word文档,见1.6.7.4节)和.NET Framework Design Guidelines : Event Design对事件行为几乎没有什么可说的。该规范观察到:

The notion of raising an event is precisely equivalent to invoking the delegate represented by the event—thus, there are no special language constructs for raising events.



C# Programming Guide : Events部分说:

When an event has multiple subscribers, the event handlers are invoked synchronously when an event is raised. To invoke events asynchronously, see Calling Synchronous Methods Asynchronously.



所以经典事件传统上是通过在单个线程上调用委托(delegate)链来串行发出的,但是在指导方针中没有这样的限制——偶尔我们会看到委托(delegate)的并行调用——但即使在这里,一个事件的两个实例通常也会串行引发甚至如果每个都并行广播。

我在官方规范中找不到任何明确说明事件实例本身必须连续引发或接收的地方。换句话说,没有什么说一个事件的多个实例不能同时引发。

这与在 Rx Design Guidelines 中明确说明的 observables 形成对比。我们应该:

4.2. Assume observer instances are called in a serialized fashion



请注意,此语句仅针对单个订阅者实例的观点,而没有提及跨实例并发发送的事件(这实际上在 Rx 中非常常见)。

所以有两条路:
  • 虽然 OnNext捕捉事件的想法 经典的 .NET 事件可能会通过并发调用事件来违反 Rx 语法。
  • 纯 Rx Observables 在负载下的事件传递有不同的语义是很常见的,因为背压通常是按订阅者而不是按事件处理的。

  • 只要您不在 API 中同时引发事件,并且您不关心背压语义,然后通过像 Rx 的 Observable.FromEvent 之类的机制转换为可观察的接口(interface)会没事的。

    逆变换

    关于逆变换,注意OnErrorOnCompleted在经典的 .NET 事件中没有类似物,因此如果没有一些额外的机制和约定的用法,就不可能进行反向映射。

    例如,可以翻译 OnErrorOnCompleted到其他事件 - 但这绝对超出了经典事件领域。此外,在不同的处理程序之间需要一些非常笨拙的同步机制;在 Rx 中,一个订阅者很可能收到 OnCompleted而另一个仍在接收 OnNext事件 - 在经典的 .NET 事件转换中实现这一点要困难得多。

    错误

    考虑错误情况下的行为:将事件源中的错误与处理程序/订阅者中的错误区分开来很重要。 OnError有没有处理前者,在后一种情况下,经典事件和 Rx 都简单地(并且正确地)爆炸了。这方面的错误确实可以在任一方向上很好地转化。

    结论

    .NET 经典事件和 Observable 不是同构的。只要您坚持正常的使用模式,您就可以相当容易地从事件转换为可观察的。可能的情况是,您的 API 需要使用 .NET 事件建模的 observables 的附加语义,因此仅使用 Observable 更有意义 - 但这是一个特定的考虑因素,而不是一般的考虑因素,更多的是一种设计问题比技术问题。

    作为一般指导,我建议尽可能优先选择经典事件,因为这些事件被广泛理解并得到很好的支持并且可以转换 - 但如果您需要以优雅形式表示的源错误或完成的额外语义,请不要犹豫使用可观察对象OnError 和 OnCompleted 事件。

    关于c# - 在面向 .NET 4+ 的库中公开通知时,IObservable 是否应该优先于事件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24572366/

    有关c# - 在面向 .NET 4+ 的库中公开通知时,IObservable 是否应该优先于事件的更多相关文章

    1. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

      作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

    2. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

      我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

    3. ruby - 如何模拟 Net::HTTP::Post? - 2

      是的,我知道最好使用webmock,但我想知道如何在RSpec中模拟此方法:defmethod_to_testurl=URI.parseurireq=Net::HTTP::Post.newurl.pathres=Net::HTTP.start(url.host,url.port)do|http|http.requestreq,foo:1endresend这是RSpec:let(:uri){'http://example.com'}specify'HTTPcall'dohttp=mock:httpNet::HTTP.stub!(:start).and_yieldhttphttp.shou

    4. c# - 如何在 ruby​​ 中调用 C# dll? - 2

      如何在ruby​​中调用C#dll? 最佳答案 我能想到几种可能性:为您的DLL编写(或找人编写)一个COM包装器,如果它还没有,则使用Ruby的WIN32OLE库来调用它;看看RubyCLR,其中一位作者是JohnLam,他继续在Microsoft从事IronRuby方面的工作。(估计不会再维护了,可能不支持.Net2.0以上的版本);正如其他地方已经提到的,看看使用IronRuby,如果这是您的技术选择。有一个主题是here.请注意,最后一篇文章实际上来自JohnLam(看起来像是2009年3月),他似乎很自在地断言RubyCL

    5. C# 到 Ruby sha1 base64 编码 - 2

      我正在尝试在Ruby中复制Convert.ToBase64String()行为。这是我的C#代码:varsha1=newSHA1CryptoServiceProvider();varpasswordBytes=Encoding.UTF8.GetBytes("password");varpasswordHash=sha1.ComputeHash(passwordBytes);returnConvert.ToBase64String(passwordHash);//returns"W6ph5Mm5Pz8GgiULbPgzG37mj9g="当我在Ruby中尝试同样的事情时,我得到了相同sha

    6. ruby - Net::HTTP 获取源代码和状态 - 2

      我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur

    7. ruby-on-rails - 事件管理员日期过滤器日期格式自定义 - 2

      是否有简单的方法来更改默认ISO格式(yyyy-mm-dd)的ActiveAdmin日期过滤器显示格式? 最佳答案 您可以像这样为日期选择器提供额外的选项,而不是覆盖js:=f.input:my_date,as::datepicker,datepicker_options:{dateFormat:"mm/dd/yy"} 关于ruby-on-rails-事件管理员日期过滤器日期格式自定义,我们在StackOverflow上找到一个类似的问题: https://s

    8. ruby - 使用 `+=` 和 `send` 方法 - 2

      如何将send与+=一起使用?a=20;a.send"+=",10undefinedmethod`+='for20:Fixnuma=20;a+=10=>30 最佳答案 恐怕你不能。+=不是方法,而是语法糖。参见http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html它说Incommonwithmanyotherlanguages,Rubyhasasyntacticshortcut:a=a+2maybewrittenasa+=2.你能做的最好的事情是:

    9. 基于C#实现简易绘图工具【100010177】 - 2

      C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

    10. Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting - 2

      1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里

    随机推荐