【问题标题】:Advantages of .NET Rx over classic events?.NET Rx 相对于经典事件的优势?
【发布时间】:2009-07-31 13:26:44
【问题描述】:

.NET 4.0 beta 2 具有 introduced IObservableIObserver 接口。

与经典 .NET 事件相比有哪些优势?这不是解决同样的问题吗?

【问题讨论】:

  • 从跨技术的角度来看,Rx 可以(并且已经)被实现以将相同的概念引入不支持开箱即用事件的语言和运行时; Java 和 JavaScript 是明显的候选者。我认为这是跨技能组合熟悉的一个很好的例子

标签: .net events system.reactive reactive-programming


【解决方案1】:

您可以将 IObservable 用作事件,将暴露事件的代码替换为 IObservable 类型的属性,但这并不是重点。

关于 IObservable 有两点需要了解:

  1. 它统一了我们之前不知道如何统一的两个概念:异步操作(通常返回单个值)和事件(通常永远持续下去)。 p>

  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。鉴于后程序员没有 Comp Sci 学位,我无法决定,如果 Rx 是不是太远了……
【解决方案2】:

我不确定优势,但我发现与经典 .NET 事件有以下区别:

错误通知

经典事件为此需要一个单独的事件,或者需要检查具有 Error 属性的 EventArgs 类。

通知结束通知

经典事件需要一个单独的事件,或者需要检查具有 Final 属性的 EventArgs 类。

【讨论】:

    【解决方案3】:

    它只是基于事件的编程模型的扩展。你创建了一些实现 IObserver 的东西,基本上你是在说“当集合中的某些东西发生变化时,这就是我想要发生的事情”。这样一来,它只是我们对事件所做的一切的标准化。

    与 IEnumerable 模式相比,他们正在推动它,就像它是一个大转折点。 IEnumerable 是“拉”,而 IObservable 是“推”。

    我看到的与直接事件相比的唯一优势是它是一个标准化的界面。我在这里看到与 ObservableCollection 有很大的重叠(和 INotifyCollectionChanged)。也许他们正在尝试采用 .NET 的 PERL 座右铭:“有不止一种方法可以做到这一点”。

    【讨论】:

    • Scott 那么问题来了,如果 IObservable 和 IObserver 在框架的版本 1 中实现,框架会是什么样子。错误处理和事件编程本来是可组合的。处理异步和同步事件的常用方法。并行扩展将使用 IObservable 类型。但最重要的是,这一切从一开始都是可组合的,这将大大简化许多现在很难甚至几乎不可能的项目。 (单元测试异步 UI 是我脑海中浮现的一种)
    • @Doughlas,但我们不能重写历史,所以问题不是“theOldWay or Rx”,而是“theOldWay OR (theOldWay and Rx)”
    • IObservable 的主要优点是它的可组合性。由于您拥有大量依赖 IObservable 接口的构建 it 运算符,因此您可以以非常简单的方式构建非常复杂的交互。对于原始事件,您将不得不借助大量状态来跟踪所有事件调用。
    【解决方案4】:

    您绝对应该观看Rx Workshop: Observables versus Events 视频和 完成随附的挑战

    【讨论】:

      最近更新 更多