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
Ok guys, seeing as how I think it's at least worth a shot to try this, and seeing as how RX's Subject
I call the class Observable
///
/// Represents a value whose changes can be observed.
///
/// The type of value.
public class Observable : IObservable, INotifyPropertyChanged
{
private T value;
private readonly List observers = new List(2);
private PropertyChangedEventHandler propertyChanged;
///
/// Constructs a new observable with a default value.
///
public Observable()
{
}
public Observable(T initalValue)
{
this.value = initialValue;
}
///
/// Gets the underlying value of the observable.
///
public T Value
{
get { return this.value; }
set
{
var valueHasChanged = !EqualityComparer.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"));
}
}
}
///
/// Converts the observable to a string. If the value isn't null, this will return
/// the value string.
///
/// The value .ToString'd, or the default string value of the observable class.
public override string ToString()
{
return value != null ? value.ToString() : "Observable<" + typeof(T).Name + "> with null value.";
}
///
/// Implicitly converts an Observable to its underlying value.
///
/// The observable.
/// The observable's value.
public static implicit operator T(Observable input)
{
return input.Value;
}
///
/// Subscribes to changes in the observable.
///
/// The subscriber.
/// A disposable object. When disposed, the observer will stop receiving events.
public IDisposable Subscribe(IObserver 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 Observer { get; private set; }
public AnonymousObserver(IObserver observer)
{
this.Observer = observer;
}
public void Dispose()
{
this.Observer = null;
}
}
}
Consuming is nice and easy. No plumbing!
public class Foo
{
public Foo()
{
Progress = new Observable();
}
public Observable Progress { get; private set; }
}
Usage is 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;
You can databind to it in WPF or Silverlight, just bind to the Value property.
Most importantly, you can compose, filter, project, and do all the sexy things RX lets you do with IObservables:
Filtering events:
foo.Progress
.Where(val => val == 100)
.Subscribe(_ => MyProgressFinishedHandler());
Automatic unsubscribe after N invocations:
foo.Progress
.Take(1)
.Subscribe(_ => OnProgressChangedOnce());
Composing events:
// Pretend we have an IObservable called IsClosed:
foo.Progress
.TakeUntil(IsClosed.Where(v => v == true))
.Subscribe(_ => ProgressChangedWithWindowOpened());
Nifty stuff!