Подтвердить что ты не робот

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

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

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

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

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

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

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

Мои вопросы для вас прекрасные люди StackOverflow:

  • Было бы хорошо/стоит заменить стандартные .NET-события на IObservable <T> значения?
  • Если бы я использовал наблюдаемый, какой бы я использовал здесь? Очевидно, мне нужно нажать на него значения (например, Progress.UpdateValue(...) или что-то еще).
4b9b3361

Ответ 1

Я не рекомендую управлять собственным списком подписчиков, когда есть встроенные темы, которые могут сделать это для вас. Он также устраняет необходимость переноса вашей собственной изменяемой копии 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

Для # 2 наиболее простой способ - через Subject:

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, он "мертв" - вы не можете размещать в нем что-либо еще.

Ответ 3

Я сделаю это коротко и просто:

  • да
  • BehaviorSubject

:)

Ответ 4

Хорошо, ребята, видя, как я думаю, что это, по крайней мере, стоит того, чтобы попробовать это и увидеть, как RX Subject <T> не совсем то, что я ищу, я создал новое наблюдаемое, которое соответствует моим потребностям:

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

Я вызываю класс Observable <T> .

Декларация:

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

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

Ответ 5

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

    public event EventHandler ProgressChanged = delegate {};

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

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

-Oisin