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:
- ¿Sería bueno / valioso reemplazar los eventos .NET estándar con los valores IObservable <T>?
- 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:
- sí
- 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.