c# events system.reactive

c# - Rx-¿Puedo/debería reemplazar eventos.NET con Observables?



events system.reactive (5)

Además del hecho de que tu código de evento podría ser más exclusivo:

public event EventHandler ProgressChanged = delegate {}; ... set { ... // no need for null check anymore ProgressChanged(this, new EventArgs()); }

Creo que al cambiar a Observable<int> simplemente está trasladando la complejidad del llamado al llamante. ¿Qué pasa si solo quiero leer el int?

-Oisin

Teniendo en cuenta los beneficios de los eventos compostables ofrecidos por el marco de Reactive Extensions (Rx) , me pregunto si mis clases deberían dejar de presionar a los eventos .NET, y en su lugar exponer Rx observables.

Por ejemplo, tome la siguiente clase usando eventos .NET estándar:

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

Gran cantidad de fontanería monótona.

En su lugar, esta clase podría usar algún tipo de observable para reemplazar esta funcionalidad:

public class Foo { public Foo() { this.Progress = some new observable; } public IObservable<int> Progress { get; private set; } }

Mucho menos fontanería La intención ya no está oscurecida por los detalles de fontanería. Esto parece beneficioso.

Mis preguntas para usted, la gente de StackOverflow es:

  1. ¿Sería bueno / valioso reemplazar los eventos .NET estándar con los valores IObservable <T>?
  2. Si tuviera que usar un observable, ¿qué tipo usaría aquí? Obviamente tengo que pasarle valores (por ejemplo, Progress.UpdateValue (...) o algo así).

Lo mantendré corto y simple:

  1. BehaviorSubject

:)


No recomiendo administrar su propia lista de suscriptores cuando hay temas integrados que pueden hacer eso por usted. También elimina la necesidad de llevar su propia copia mutable de T.

A continuación está mi versión (sin comentarios) de su solución:

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


Ok chicos, viendo que creo que al menos vale la pena probar esto, y viendo cómo el Subject <T> de RX no es exactamente lo que estoy buscando, he creado un nuevo observable que se ajusta a mis necesidades:

  • Implementa IObservable <T>
  • Implementa INotifyPropertyChange para que funcione con el enlace WPF / Silverlight.
  • Proporciona una semántica fácil de obtener / establecer.

Llamo a la clase Observable <T>.

Declaración:

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

Uso:

El consumo es agradable y fácil. ¡Sin plomería!

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

El uso es 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;

Puede enlazar datos en WPF o Silverlight, solo enlazar a la propiedad Value.

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

Lo más importante es que puedes componer, filtrar, proyectar y hacer todas las cosas sexy que RX te permite hacer con IObservables:

Eventos de filtrado:

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

Anulación de suscripción automática luego de N invocaciones:

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

Componer eventos:

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

¡Cosas ingeniosas!


Para el n. ° 2, la forma más directa es a través de un Asunto:

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

Con esto, ahora su "Progreso" no solo puede notificar cuándo ha cambiado el progreso, sino cuándo se ha completado la operación y si fue exitoso. Tenga en cuenta, sin embargo, que una vez que un IObservable se ha completado a través de OnCompleted o OnError, está "muerto"; no puede publicar nada más.