Recently, I came across a problem that was easily solved with Observables implemented with the IObservable and IObserver interfaces in .NET. However, the end-user developer was unfamiliar with observables and to introduce a new pattern would break consistency in the larger application. To encapsulate the reactive solution, an event-Observable bridge was created to expose the observable as an EventHandler .
My first attempt was to have an observer publish messages to the event handler by invoking it. This works and didn't seem to be an issue.
public event EventHandler<T> MyEvent;
private readonly IObservable<T> _myObservable;
private readonly List<IDisposable> _subscriptions = new List<IDisposable>();
// ... some code
public Constructor()
{
Action<T> onNext = message => MyEvent?.Invoke(this, message);
// ... _myObservable is initialized, etc...
_myObservable.Subscribe(new Observer<T>(onNext));
_subscriptions.Add(disposable);
}
public void Dipose() {
_subscriptions.ForEach(disposable => disposable.Dispose());
}
However, one thing that bothered me was the physical code separation. To someone maintaining this code, it's not clear that the EventHandler is coupled to the observable until you read all of the code. Ideally, things that are coupled together should belong together. It makes it easier, when you come back a few years down the road, to be able to reason and see the coupling in one place.
A pretty nice solution is to declare the subscription within the EventHandler:
public event EventHandler<T> MyEvent {
add {
Action<T> onNext = message => value(this, message);
var disposable = _myObservable.Subscribe(new Observer<T>(onNext));
_subscriptions.Add(value, disposable);
}
remove {
_subscriptions[value].Dispose();
_subscriptions.Remove(value);
}
}
private readonly IObservable<T> _myObservable;
private readonly IDictionary<EventHandler<T>, IDisposable> _subscriptions =
new Dictionary<EventHandler<T>, IDisposable>();
A bonus is that the subscription would be removed automatically when the event handler is removed. In the previous attempt, the subscriptions would only be disposed when the parent object was disposed.
This was pretty awesome as it mimics the design of micro-services where the implementation details are encapsulated behind a unified interface.
Resources
- Header image by Anneli Salo, CC BY-SA 3.0, via Wikimedia Commons