I remember one of the things that got me excited when I first read about the as-then new fangled C# and .NET stuff coming out of Microsoft was the idea that a single event could have multiple handlers.
Cutting a long and irrelevant story short, my interest in .NET waned, although my interest in multicast events did not. For a long time I was resigned to having to do without them, but then realised that this was a self imposed penury.
So I got out my jail breaking toolkit (Delphi) and set about tearing down the walls of my prison. And here’s how I did it…
Just To Tease You
Before picking apart the implementation, I shall first demonstrate some of the benefits that my multicast implementation has brought to my own projects.
The juicy stuff – how it all works and, eventually, a link so you can download and use it yourself – will come later.
So first, the basics – adding and removing handlers for an event. I shall use a button class that supports a multicast version of the usual OnClick event. In my own use, I distinguish between unicast and multicast events by naming multicast events with an underscore following the conventional “On” prefix.
procedure TSomeObject.CustomClickResponse(Sender: TObject); begin // Some additional handling of an event... end; // Adds my custom click handler to a button's On_Click event fButton.On_Click.Add( CustomClickResponse ); // ... and this removes it again fButton.On_Click.Remove( CustomClickResponse );
When adding a handler to an event that may already have a handler, this conveniently does away with all the business of remembering the previous handler reference, remembering to call that previous handler at the end of the new processing and (hopefully) remembering to restore the old handler if we subsequently wish to remove our additional handler.
Another thing that is often needed with event handlers is to disable an event while some processing takes place, usually in response that event itself. For example if we wish to modify the contents of an edit control and do not want any Change event to fire while we do so, ordinarily this will involve something like:
var oldChangeHandler: TNotifyEvent; begin oldChangeHandler := fEdit.OnChange; fEdit.OnChange := NIL; try // Change edit control content without triggering change event fEdit.Text := 'Some New Value'; finally fEdit.OnChange := oldChangeHandler; end; end;
With my multicast events I can simply disable the event itself:
fEdit.On_Change.Enabled := FALSE; try // Change edit control content without triggering change event fEdit.Text := 'Some New Value'; finally fEdit.On_Change.Enabled := TRUE; end;
One problem that can easily crop up is what happens if a handler is added to an event and then the object that implements the handler get’s destroyed? The next time the event fires an error is likely to occur when the handler on the destroyed object is invoked.
My implementation even copes with this, and does so largely transparently. There is a little work involved but really not much, requiring the implementation of an interface with a single method on classes that implement event handlers. The details of which I will go into when we delve into the implementation of these events themselves.
Speaking of which, using these events is clearly quite straight-forward but what about implementing support for the events themselves?
Well, that too is very straight-forward.
A base class provides the basic behaviours needed – managing the list of handlers for the event and the enabled/disabled state of the event.
A specialisation of this class is needed for each event signature for which I want to have multicast support. In practice I have found that a multicast version of TNotifyEvent covers the vast (and I mean overwhelmingly vast) majority of cases where multicast events are useful. Not every event type is going to have a useful existence in a multicast form.
These specialisations are fairly trivial, as we shall see when we come to them in future posts.
I shall go through the step-by-step building up of functionality and features in the framework as it exists today – it won’t take long, amounting to less than 900 lines of code in all, including copious documentation (I use Doc-o-Matic, so an investment in well documented code pays dividends in usable reference documentation later).
For now, one last piece of groundwork…
Anatomy of a Method
What exactly is a Method? Let us consider the hopefully familiar TNotifyEvent:
TNotifyEvent = procedure (Sender: TObject) of object;
This immediately highlights the difference between an event procedure type and regular Pascal procedure types – that innocuous “of object”, signifying that a method can only satisfy this type signature if it is some method of a class.
Under the covers a reference to a handler for this type of event is required to identify not one, but two things:
1. The method to be executed
2. The object instance that the method is to be execute on
That 2nd item of information of course ends up being the “self” that the event handler code may reference. For the most part, the details are taken care of by the compiler and the runtime. The Delphi runtime library even defines a type encapsulating a method reference, though you may never have come across it as ordinarily it has very limited use on its own:
TMethod = record Code, Data: Pointer; end;
And believe it or not, armed with this knowledge we now have all we need to implement our multicast events.