Rx - можно / нужно ли заменить .NET-события на Observables?

Учитывая преимущества компонуемых событий, предлагаемые платформой 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 Progress { get; private set; }
}

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

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

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

37
задан Judah Gabriel Himango 31 August 2010 в 19:07
поделиться

3 ответа

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

19
ответ дан 27 November 2019 в 05:01
поделиться

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

    public event EventHandler ProgressChanged = delegate {};

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

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

-Ойсин

0
ответ дан 27 November 2019 в 05:01
поделиться

Хорошо, ребята, учитывая, что, по моему мнению, по крайней мере стоит попробовать это, и учитывая, что Subject 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());

Отличная штука!

2
ответ дан 27 November 2019 в 05:01
поделиться
Другие вопросы по тегам:

Похожие вопросы: