37 votes

Rx - Puis-je / dois-je remplacer les événements .NET par Observables?

Étant donné les avantages de la composable événements tel que proposé par le Réactif Extensions (Rx) du cadre, je me demande si mes classes devraient arrêter de pousser .NET événements, et au lieu d'exposer Rx observables.

Par exemple, prendre la classe suivante à l'aide de la norme .NET événements:

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

Beaucoup de monotone de la plomberie.

Cette classe pourrait plutôt utiliser une sorte d'observables pour remplacer cette fonctionnalité:

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

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

Beaucoup moins de plomberie. L'Intention n'est plus obscurci par de plomberie de détails. Cela semble bénéfique.

Mes questions pour vous bien StackOverflow gens sont:

  1. Serait-il bon de le/la peine de remplacer la norme .NET événements avec IObservable<T> les valeurs?
  2. Si je devais utiliser une observable, quel type dois-je utiliser ici? Évidemment, j'ai besoin de pousser des valeurs (par exemple, le Progrès.UpdateValue(...) ou quelque chose).

19voto

Paul Betts Points 41354

Pour # 2, la manière la plus simple est via un sujet:

 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);
}
 

Avec cela, votre "Progression" peut désormais non seulement notifier quand la progression a changé, mais quand l'opération est terminée et si elle a réussi. Gardez à l'esprit cependant qu'une fois qu'un IObservable est terminé via OnCompleted ou OnError, il est "mort" - vous ne pouvez rien publier de plus.

7voto

Richard Szalay Points 42486

Je ne recommande pas de gérer votre propre liste d'abonnés lorsqu'il existe des sujets intégrés qui peuvent le faire pour vous. Il supprime également la nécessité de transporter votre propre copie mutable de T.

Voici ma version (sans commentaire) de votre solution:

 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; } 
    } 
}
 

3voto

Omer Mor Points 3658

Je serai bref et simple:

  1. Oui
  2. BehaviorSubject

:)

2voto

Judah Himango Points 27365

Ok les gars, en voyant comment je pense que c'est au moins vaut le coup d'essayer, et de voir comment RX Sujet<T> n'est pas tout à fait ce que je cherche, j'ai créé un nouveau observables qui correspond à mes besoins:

  • Met En Œuvre IObservable<T>
  • Met en œuvre INotifyPropertyChange de travailler avec WPF/Silverlight de liaison.
  • Fournit facile d'obtenir/définir la sémantique.

J'appelle la classe Observable<T>.

Déclaration:

/// <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;
        }
    }
}

Utilisation:

Consommer est agréable et facile. Pas de plomberie!

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

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

L'utilisation est simple:

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

Vous pouvez databind en WPF ou Silverlight, juste lier à la Valeur de la propriété.

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

Plus important encore, vous pouvez composer, de filtre, d'un projet, et faire toutes les choses sexy RX vous permet de le faire avec IObservables:

Le filtrage des événements:

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

Désabonnement automatique après N invocations:

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

La composition des événements:

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

Chouettes trucs!

0voto

x0n Points 26002

Mis à part le fait que votre code d'événement existant pourrait être plus tordu:

     public event EventHandler ProgressChanged = delegate {};

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

Je pense qu'en passant à Observable<int> vous déplacez simplement la complexité de l'appelé vers l'appelant. Et si je veux juste lire l'int?

-Oisine

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X