Rx - можно / нужно заменить события .NET на Observables? - PullRequest
37 голосов
/ 25 августа 2010

Учитывая преимущества компонуемых событий, предлагаемые платформой Reactive Extensions (Rx) , мне интересно, должны ли мои классы прекратить проталкивать события .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());
            }
         }
      }
   }
}

Лот монотонной сантехники.

Этот класс может вместо этого использовать некоторую наблюдаемую для замены этой функциональности:

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

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

Гораздо меньше сантехники.Намерение больше не заслоняется деталями водопровода.Это кажется полезным.

Мои вопросы к вам, ребята из StackOverflow:

  1. Было бы целесообразно / стоит заменить стандартные события .NET на IObservable значения?
  2. Если бы я использовал наблюдаемые, какой тип я бы использовал здесь?Очевидно, мне нужно передать значения (например, Progress.UpdateValue (...) или что-то).

Ответы [ 5 ]

19 голосов
/ 25 августа 2010

Для # 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, он «мертв» - вы не можете публиковать что-либо еще.

7 голосов
/ 13 сентября 2010

Я не рекомендую управлять своим списком подписчиков, когда есть встроенные темы, которые могут сделать это для вас.Это также устраняет необходимость носить собственную изменяемую копию 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; } 
    } 
}
3 голосов
/ 06 декабря 2010

Я буду кратким и простым:

  1. да
  2. BehaviorSubject

:)

2 голосов
/ 26 августа 2010

Хорошо, ребята, видя, как я думаю, что по крайней мере стоит попробовать это, и видя, как субъект RX не совсем то, что я ищу, я создал новую наблюдаемую, которая подходитмои потребности:

  • Реализует IObservable
  • Реализует INotifyPropertyChange для работы с привязкой WPF / Silverlight.
  • Обеспечивает легкую семантику получения / установки.

Я называю класс Observable .

Декларация:

/// <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());

Отличный материал!

0 голосов
/ 25 августа 2010

Помимо того, что ваш существующий код событий может быть более кратким:

    public event EventHandler ProgressChanged = delegate {};

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

Я думаю, что переключаясь на Observable<int>, вы просто перемещаете сложность от вызываемого к вызывающему. Что если я просто хочу прочитать инт?

-Oisin

...