Given the benefits of composable events as offered by the Reactive Extensions (Rx) framework, I'm wondering whether my classes should stop pushing .NET events, and instead expose Rx observables.
For instance, take the following class using standard .NET events:
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()); } } } } }
Lot of monotonous plumbing.
This class could instead use some sort of observable to replace this functionality:
public class Foo { public Foo() { this.Progress = some new observable; } public IObservable<int> Progress { get; private set; } }
Far less plumbing. Intention is no longer obscured by plumbing details. This seems beneficial.
My questions for you fine StackOverflow folks are:
For #2, the most straightforward way is via a 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); }
With this, now your "Progress" can not only notify when the progress has changed, but when the operation has completed, and whether it was successful. Keep in mind though, that once an IObservable has completed either via OnCompleted or OnError, it's "dead" - you can't post anything further to it.
I don't recommend managing your own subscriber list when there are built in subjects that can do that for you. It also removes the need for carrying your own mutable copy of T.
Below is my (commentless) version of your 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; } } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With