.NET Rx相比经典事件有哪些优势?

29

从跨技术的角度来看,Rx可以(并且已经)被实现,将相同的概念带到不支持事件的语言和运行时环境中;Java和JavaScript是显而易见的候选。我认为这是技能集之间熟悉度的一个很好的例子。 - Matthew Layton
4个回答

37
你可以使用IObservable作为事件,用IObservable类型的属性替换暴露事件的代码,但这并不是重点。
关于IObservable有两个重要的事情需要理解:
1.它统一了以前无法统一的两个概念:异步操作(通常返回单个值)和事件(通常持续进行)。
2.它是可组合的。与CLR事件、IAsyncResult或INotifyCollectionChanged不同,它允许我们从通用事件和异步操作构建特定事件。
下面是我在工作中遇到的一个例子。
在Silverlight中,有些效果可以应用于图像控件,但无法应用于普通控件。为了绕过这些限制,当控件内容更改时,我可以等待其视觉外观更新并截取屏幕快照。然后我想隐藏其视觉表示,将其替换为快照,并对图像应用视觉效果。现在,我可以对控件应用图像效果(假设它不是交互式的)。
这个程序很简单,但必须是异步的。我必须等待两个连续的异步操作完成,然后才能对图像应用效果:
1.控件内容已更改
2.控件的视觉外观已更新
这是我如何使用Rx解决此问题的方法:
// A content control is a control that displays content.  That content can be
// anything at all like a string or another control.  Every content control contains
// another control: a ContentPresenter.  The ContentPresenter's job is to generate
// a visual representation of the Content property. For example, if the Content property
// of the ContentControl is a string, the ContentPresenter creates a TextBlock and inserts
// the string into it.  On the other hand if the Content property is another control the 
// ContentPresenter just inserts it into the visual tree directly.
public class MyContentControl : ContentControl
{
   // A subject implements both IObservable and IObserver.  When IObserver methods
   // are called, it forwards those calls to all of its listeners.
   // As a result it has roughly the same semantics as an event that we can "raise."
   private Subject<object> contentChanged = new Subject<object>();

   // This is a reference to the ContentPresenter in the ContentControl's template
   private ContentPresenter contentPresenter; 

   // This is a reference to the Image control within ContentControl's template.  It is displayed on top of the ContentPresenter and has a cool blur effect applied to it.
   private Image contentImageControl; 

   public MyContentControl()
   {
      // Using Rx we can create specific events from general events.
      // In this case I want to create a specific event ("contentImageChanged") which
      // gives me exactly the data I need to respond and update the UI.
      var contentImageChanged = 
         // get the content from the content changed event
         from content in contentChanged
         where content != null
         // Wait for the ContentPresenter's visual representation to update.
         // ContentPresenter is data bound to the Content property, so it will
         // update momentarily.
         from _ in contentPresenter.GetLayoutUpdated().Take(1)
         select new WritableBitmap(contentPresenter, new TranslateTransform());

      contentImageChanged.Subscribe(
         contentImage => 
         {
            // Hide the content presenter now that we've taken a screen shot              
            contentPresenter.Visibility = Visibility.Collapsed; 

            // Set the image source of the image control to the snapshot
            contentImageControl.ImageSource = contentImage;
         });
   }

   // This method is invoked when the Content property is changed.
   protected override OnContentChanged(object oldContent, object newContent)
   {
      // show the content presenter before taking screenshot
      contentPresenter.Visibility = Visibility.Visible;  

      // raise the content changed "event"
      contentChanged.OnNext(newContent);   

      base.OnContentChanged(oldContent, newContent);
   }
}

这个例子特别简单,因为只有两个连续的操作序列。即使在这个简单的示例中,我们也可以看到 Rx 的价值。如果没有它,我将不得不使用状态变量来确保事件按照特定顺序触发。我还必须编写一些非常丑陋的代码来显式地从 LayoutUpdated 事件分离。

当你使用 Rx 进行编程时,关键是要思考“我希望我的框架提供什么事件?”然后去创建它。我们通常认为事件是简单的、由输入驱动的事物(例如“mouseover”、“mouseclick”、“keyup”等)。但是,事件也可以是非常复杂和特定于您的应用程序的(例如“GoogleMsdnMashupStockDataArrived”、“DragStarting”和“ImageContentChanged”)。当您以这种方式组织程序时(精确地创建我需要的事件,然后通过更改状态响应它),您会发现它们具有更少的状态错误,更有序,并且总体上更容易理解。

明白了吗? :-)


很好的例子(因此+1),但我认为我更想看到状态机,因为在所有程序员理解Rx之前,这将需要很长时间。鉴于大多数程序员没有计算机科学学位,我无法确定Rx是否太过高级了... - Ian Ringrose

4
我不确定有何优势,但我发现与经典的.NET事件相比,以下是一些不同之处: 错误通知 传统事件需要单独的事件或者一个带有Error属性需要检查的EventArgs类。 通知结束通知 传统事件需要单独的事件或者一个带有Final属性需要检查的EventArgs类。

3
这只是事件驱动编程模型的扩展。您创建一个实现IObserver接口的东西,基本上就是在说“当集合中的某些内容发生更改时,我想要发生什么”。这样,它只是我们一直以来在处理事件时的标准化。
他们正在推销它,好像与IEnumerable模式相比,这是一个大的转变。IEnumerable是“拉”,而IObservable是“推”。
我唯一看到的优势是它是一个标准化的接口。但我在这里看到了很大的重叠(和INotifyCollectionChanged)与ObservableCollection。也许他们正试图采用.NET的PERL座右铭:“有多种方法可以做到这一点”。

4
如果在 Version 1 中实现 IObservable 和 IObserver 接口,框架会是什么样子呢?错误处理和事件编程将变得可组合。 这是一种处理异步和同步事件的常见方式。Parallel Extensions 也将使用 IObservable 类型。但主要的问题在于,从一开始就可以将所有内容组合起来,这将大大简化许多现在很难甚至几乎不可能完成的任务。(例如单元测试异步 UI)注意:本翻译尽量忠实地传达原文意思,但针对某些技术语言或代码段,若读者对其理解有歧义,建议参考原文。 - DouglasH
1
@道格拉斯,但我们无法重写历史,所以问题不是“theOldWay还是Rx”,而是“theOldWay或(theOldWay和Rx)”。 - Ian Ringrose
IObservable的主要优势在于其可组合性。由于您拥有大量依赖于IObservable<T>接口的内置操作符集合,因此可以以非常简单的方式构建非常复杂的交互。使用原始事件,您将不得不采用大量状态来跟踪所有事件调用。 - ionoy

2

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