Events, callbacks, observers, whatever you want to call them – the need to dynamically inject a block of executable code into another is a problem that arises in most software systems. Different programming languages and frameworks have addressed this differently. Java, for example, lacking anything like a function pointer does everything through callback interfaces. .NET on the other hand based it around the idea of a Delegate, a type safe function pointer that can be attached to an Event.
An Event looks very much like a property of a delegate type, and omitting the event keyword will actually work just fine. The only thing event does (when not using custom add and remove overrides) is to prevent anyone outside of the class to set the delegate directly. You are free to add and remove event handlers, but you can’t, say, remove all event handlers in one operation.
interface IMailbox { event EventHandler MessageReceived; } class MailboxNotifier { public MailboxNotifier(IMailbox mailbox) { mailbox.MessageReceived += HandleMessageReceived; } private void HandleMessageReceived(object sender, EventArgs e) { MessageBox.Show("You've got mail"); } }
Easy! Maybe a bit too easy. The number one cause of memory leaks in the .NET world is event handlers that have not been unregistered. For this specific case, we also most likely want a way of stopping this thing popping up annoying message boxes every time you get an email. To fix this, we unregister from the event, in the same style as when we added our handler.
class MailboxNotifier : IDisposable { private readonly IMailbox mailbox; public MailboxNotifier(IMailbox mailbox) { this.mailbox = mailbox; mailbox.MessageReceived += HandleMessageReceived; } public void Dispose() { mailbox.MessageReceived -= HandleMessageReceived; } private void HandleMessageReceived(object sender, EventArgs e) { MessageBox.Show("You've got mail"); } }
Ignoring the fact that I cheated and didn’t properly implement the Dispose Pattern, this looks just fine doesn’t it? Well, it works, but we have been forced to keep track of the instance of the mailbox. This means that 1) even if nothing is using the mailbox, it will still not be garbage collected as long as we keep this reference and 2) some rather mundane typing just for the sake of being good citizens and cleaning up after ourselves.
It gets even worse though. .NET 2 (yes, that was ages ago) brought anonymous functions to the language, C# 3 further refined the syntax. Using the C# 3 lambda syntax, the example above can be rewritten as
class MailboxNotifier { public MailboxNotifier(IMailbox mailbox) { mailbox.MessageReceived += (sender, e) => { MessageBox.Show("You've got mail"); }; } }
Awesome, less is more and we are happy. But wait, if this is the starting point, how do we clean up after ourselves?
class MailboxNotifier : IDisposable { private readonly IMailbox mailbox; public MailboxNotifier(IMailbox mailbox) { this.mailbox = mailbox; mailbox.MessageReceived += (sender, e) => { MessageBox.Show("You've got mail"); }; } public void Dispose() { mailbox.MessageReceived -= ??? } }
As the event handler is no longer named, we can’t unregister from our event! The only way to do this properly is to keep a reference to the event handler, effectively naming it and losing the benefit of the shorter syntax:
class MailboxNotifier : IDisposable { private readonly IMailbox mailbox; private readonly EventHandler messageReceivedHandler; public MailboxNotifier(IMailbox mailbox) { this.mailbox = mailbox; this.messageReceivedHandler = (sender, e) => { MessageBox.Show("You've got mail"); }; mailbox.MessageReceived += messageReceivedHandler; } public void Dispose() { mailbox.MessageReceived -= messageReceivedHandler; } }
We just want to do one basic thing but end up having to carry around an instance of the mailbox as well as an instance of our message handler, only used for cleaning up!
Luckily, some bright guys over at Microsoft thought about events and asynchronous operations and realized that IEnumerable, being a sequence of items in “space”, was very much like what they called IObservable, being a sequence of items in time. This opened up a whole bag of interesting operations and was released under the name Reactive Extensions. Among the many benefits of using IObservable over event is the fact that events/observables are now “things” that can be passed around and transformed. For this post, the important part is however that they offered a new solution to handling subscriptions.
IObservable has a method, Subscribe(IObserver observer). This is very much like the += syntax used to add an event handler in the traditional .NET model. However, there is no Unsubscribe(IObserver observer). Instead, Subscribe() returns another “thing” representing the subscription. The specific type chosen is IDisposable, which makes a good choice, but it might as well have been say just an instance of Action. The key here is that to unsubscribe, you need one thing, something representing the subscription. You do not need to keep track of either what you subscribe to, or what is handling events from the subscription. The final version of our example then, using IObservable instead of .NET events:
interface IMailbox { public IObservable<Message> MessageReceived; } class MailboxNotifier : IDisposable { private readonly IDisposable mailboxSubscription; public MailboxNotifier(IMailbox mailbox) { this.mailboxSubscription = mailbox.Subscribe(_ => { MessageBox.Show("You've got mail"); }); } public void Dispose() { this.mailboxSubscription.Dispose(); } }
We are happy.