Advantages of .NET Rx over classic events?

前端 未结 4 472
没有蜡笔的小新
没有蜡笔的小新 2021-02-01 16:24

.NET 4.0 beta 2 has introduced the IObservable and IObserver interfaces.

What are the advantages compared to classic .NET events? Doesn\'t this solve the same problem?<

4条回答
  •  迷失自我
    2021-02-01 17:21

    You can use IObservable as an event, replacing code that exposes events with properties of type IObservable, but that's not really the point.

    There are two important things to understand about IObservable:

    1. It unifies two concepts that we didn't know how to unify before: asynchronous operations (which typically return a single value) and events (which typically go on forever).

    2. It is composable. Unlike CLR events, IAsyncResult, or INotifyCollectionChanged it allows us to build specific events out of general events and asynchronous operations.

    Here's an example I ran into at work just this afternoon.

    In Silverlight there are some effects you can apply to an image control that cannot be applied to a normal control. To get around these limitations when a control's content is changed I can wait for its visual appearance to be updated and take a screenshot of it. Then I want to hide its visual representation, replace it with the snapshot, and apply the visual effects to the image. Now I can apply image effects to a control (assuming it's not interactive).

    This program would be trivial but for the fact that it must be asynchronous. I must wait for two consecutive asynchronous operations to complete before I can apply effects to the image:

    1. The control's content is changed
    2. The control's visual appearance is updated

    Here's how I'd solve this problem using 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 contentChanged = new Subject();
    
       // 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);
       }
    }
    
    
    

    This example is particularly simple given that there is only two consecutive operations to sequence. Even in this simple example though we can see that Rx adds value. Without it I would have had to have used state variables to ensure the events were firing in a certain order. I also would've had to write some pretty ugly code to explicity detach from the LayoutUpdated event.

    When you're programming with Rx the trick is to think "What event do I wish my framework provided?" and then go create it. We're trained to think about events as simple, input-driven things ("mouseover", "mouseclick", "keyup", etc). However there's no reason events can't be very complex and specific to your app ("GoogleMsdnMashupStockDataArrived", "DragStarting", and "ImageContentChanged"). When you structure your programs this way (create exactly the event I need and then respond to it by changing state) you'll find that they have fewer state bugs, become more ordered, and are altogether more self-describing.

    Got it? :-)

    提交回复
    热议问题