Rx - 可以/我应该用Observable替换.NET事件吗?

时间:2010-08-25 16:01:52

标签: c# events system.reactive

鉴于Reactive Extensions (Rx) framework提供的可组合事件的好处,我想知道我的类是否应该停止推送.NET事件,而是暴露Rx可观察事件。

例如,使用标准.NET事件采用以下类:

public class Foo
{
   private int progress;
   public event EventHandler ProgressChanged;

   public int Progress
   {
      get { return this.progress; }
      set
      {
         if (this.progress != value)
         {
            this.progress = value;

            // Raise the event while checking for no subscribers and preventing unsubscription race condition.
            var progressChanged = this.ProgressChanged;
            if (progressChanged != null)
            {
                progressChanged(this, new EventArgs());
            }
         }
      }
   }
}

很多单调的管道。

此类可以使用某种observable来替换此功能:

public class Foo
{
   public Foo()
   {
       this.Progress = some new observable;
   }

   public IObservable<int> Progress { get; private set; }
}

远不如管道。管道细节不再掩盖意图。这看起来很有用。

我的问题很好StackOverflow人员:

  1. 用IObservable&lt; T&gt;替换标准.NET事件是否合适/值得值?
  2. 如果我要使用一个可观察的,我会在这里使用什么样的?显然我需要将值推送到它(例如Progress.UpdateValue(...)或其他东西)。

5 个答案:

答案 0 :(得分:19)

对于#2,最直接的方式是通过主题:

Subject<int> _Progress;
IObservable<int> Progress {
    get { return _Progress; }
}

private void setProgress(int new_value) {
    _Progress.OnNext(new_value);
}

private void wereDoneWithWorking() {
    _Progress.OnCompleted();
}

private void somethingBadHappened(Exception ex) {
    _Progress.OnError(ex);
}

这样,现在您的“进度”不仅可以通知进度何时发生变化,还可以通知操作何时完成,以及是否成功。但请记住,一旦IObservable通过OnCompleted或OnError完成,它就“死了” - 你不能再向它发布任何内容了。

答案 1 :(得分:6)

如果内置的主题可以为您执行此操作,我建议您不要管理自己的订阅者列表。它还消除了携带自己的可变副本T的需要。

以下是我的(无评论)版本的解决方案:

public class Observable<T> : IObservable<T>, INotifyPropertyChanged 
{ 
    private readonly BehaviorSubject<T> values; 

    private PropertyChangedEventHandler propertyChanged; 

    public Observable() : this(default(T))
    {
    } 

    public Observable(T initalValue) 
    { 
        this.values = new BehaviorSubject<T>(initalValue);

        values.DistinctUntilChanged().Subscribe(FirePropertyChanged);
    }

    public T Value 
    { 
        get { return this.values.First(); } 
        set { values.OnNext(value); } 
    }

    private void FirePropertyChanged(T value)
    {
        var handler = this.propertyChanged;

        if (handler != null)
            handler(this, new PropertyChangedEventArgs("Value"));
    }

    public override string ToString() 
    { 
        return value != null ? value.ToString() : "Observable<" + typeof(T).Name + "> with null value."; 
    } 

    public static implicit operator T(Observable<T> input) 
    { 
        return input.Value; 
    } 

    public IDisposable Subscribe(IObserver<T> observer) 
    { 
        return values.Subscribe(observer);
    } 

    event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged 
    { 
        add { this.propertyChanged += value; } 
        remove { this.propertyChanged -= value; } 
    } 
}

答案 2 :(得分:3)

我会简短地说:

  1. BehaviorSubject
  2. :)

答案 3 :(得分:2)

好的家伙,看到我认为这至少值得尝试一下,看看RX的主题&lt; T&gt;不是我想要的,我创造了一个符合我需求的新观察点:

  • 实施IObservable&lt; T&gt;
  • 实现INotifyPropertyChange以使用WPF / Silverlight绑定。
  • 提供简单的get / set语义。

我将该类称为Observable&lt; T&gt;。

声明:

/// <summary>
/// Represents a value whose changes can be observed.
/// </summary>
/// <typeparam name="T">The type of value.</typeparam>
public class Observable<T> : IObservable<T>, INotifyPropertyChanged
{
    private T value;
    private readonly List<AnonymousObserver> observers = new List<AnonymousObserver>(2);
    private PropertyChangedEventHandler propertyChanged;

    /// <summary>
    /// Constructs a new observable with a default value.
    /// </summary>
    public Observable()
    {
    }

    public Observable(T initalValue)
    {
        this.value = initialValue;
    }

    /// <summary>
    /// Gets the underlying value of the observable.
    /// </summary>
    public T Value
    {
        get { return this.value; }
        set
        {
            var valueHasChanged = !EqualityComparer<T>.Default.Equals(this.value, value);
            this.value = value;

            // Notify the observers of the value.
            this.observers
                .Select(o => o.Observer)
                .Where(o => o != null)
                .Do(o => o.OnNext(value))
                .Run();

            // For INotifyPropertyChange support, useful in WPF and Silverlight.
            if (valueHasChanged && propertyChanged != null)
            {
               propertyChanged(this, new PropertyChangedEventArgs("Value"));
            }
        }
    }

    /// <summary>
    /// Converts the observable to a string. If the value isn't null, this will return
    /// the value string.
    /// </summary>
    /// <returns>The value .ToString'd, or the default string value of the observable class.</returns>
    public override string ToString()
    {
        return value != null ? value.ToString() : "Observable<" + typeof(T).Name + "> with null value.";
    }

    /// <summary>
    /// Implicitly converts an Observable to its underlying value.
    /// </summary>
    /// <param name="input">The observable.</param>
    /// <returns>The observable's value.</returns>
    public static implicit operator T(Observable<T> input)
    {
        return input.Value;
    }

    /// <summary>
    /// Subscribes to changes in the observable.
    /// </summary>
    /// <param name="observer">The subscriber.</param>
    /// <returns>A disposable object. When disposed, the observer will stop receiving events.</returns>
    public IDisposable Subscribe(IObserver<T> observer)
    {
        var disposableObserver = new AnonymousObserver(observer);
        this.observers.Add(disposableObserver);
        return disposableObserver;
    }

    event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
    {
        add { this.propertyChanged += value; }
        remove { this.propertyChanged -= value; }
    }

    class AnonymousObserver : IDisposable
    {
        public IObserver<T> Observer { get; private set; }

        public AnonymousObserver(IObserver<T> observer)
        {
            this.Observer = observer;
        }

        public void Dispose()
        {
            this.Observer = null;
        }
    }
}

用法:

消费很好,很容易。没有管道!

public class Foo
{
    public Foo()
    {
        Progress = new Observable<T>();
    } 

    public Observable<T> Progress { get; private set; }
}

用法很简单:

// Getting the value works just like normal, thanks to implicit conversion.
int someValue = foo.Progress;

// Setting the value is easy, too:
foo.Progress.Value = 42;

您可以在WPF或Silverlight中对其进行数据绑定,只需绑定到Value属性即可。

<ProgressBar Value={Binding Progress.Value} />

最重要的是,你可以编写,过滤,投射和完成RX让你用IObservables做的所有性感事物:

过滤活动:

foo.Progress
   .Where(val => val == 100)
   .Subscribe(_ => MyProgressFinishedHandler());

N次调用后自动取消订阅:

foo.Progress
   .Take(1)
   .Subscribe(_ => OnProgressChangedOnce());

撰写活动:

// Pretend we have an IObservable<bool> called IsClosed:
foo.Progress
   .TakeUntil(IsClosed.Where(v => v == true))
   .Subscribe(_ => ProgressChangedWithWindowOpened());

漂亮的东西!

答案 4 :(得分:0)

除了您现有的事件代码可能更简洁:

    public event EventHandler ProgressChanged = delegate {};

    ...
       set {
          ... 
          // no need for null check anymore       
          ProgressChanged(this, new EventArgs());
   }

我认为通过切换到Observable<int>,您只是将复杂性从被调用者转移到调用者。如果我只想读取int怎么办?

-Oisin