Most of the examples I’ve seen of Generics in Delphi, use classes containing a generic type. However, while working on a personal project, I decided I wanted an Interface containing a generic type.
The project uses an in-process publish/subscribe mechanism, and I wanted a subscriber to have a separate Receive method for each event type, rather than a single method which contained a big case statement with branches for each event type. Equally, however, I didn’t want to have to define an interface type for each event type. What I wanted was to have a generic Subscriber interface, that took the event type as a parameter.
However, I had no idea if I could define a generic Interface, let alone implement one. Even assuming I could do both of those things, would Delphi be able to resolve the correct Receive method to invoke? Only one way to find out….
NB: In this example, I’ve stripped out most of the pub/sub stuff, just leaving the pieces needed to show the generic interfaces. I’ll write about the other pieces over the next few posts.
First, I implemented a few sample events. The contents of these are not that interesting:
TSomeEvent = class
// other stuff
TSomeOtherEvent = class
// other stuff
Then, I defined a generic interface
ISubscriber = interface
procedure Receive(Event : T);
This is the interface that will need to be implemented by my subscribers in order to receive events of a particular type. Note, the type of event is set up as a generic type T.
Then, my subscribers need to implement an interface for each Event type they want to receive, however because it is a generic interface, it’s pretty straight-forward:
TMySubscribingObject = class(TInterfacedObject, ISubscriber, ISubscriber)
procedure Receive(Event : TSomeEvent); overload;
procedure Receive(Event : TSomeOtherEvent); overload;
Note there’s no definition of a ISomeEventSubscriber and a ISomeOtherEventSubscriber interface, we can just use ISubscriber<T> and pass the type in in-place. We just need to implement the necessary overloaded Receive events.
You can see the rest of the code in the associated test project, but the above shows you the main aim. Implementing multiple interfaces, each with a strongly-typed Receive event, without actually having to define each of those interfaces.
So, does it work? Well, at my first attempt, no, it didn’t. No matter what type of Event I passed in, or via what interface, it always invoked the last Receive method.
Rule of life #37: If it’s a choice between Malcolm having screwed up or the Delphi compiler architects, it’s probably Malcolm’s fault.
Yes, my bad. Barry Kelly eventually pointed out the error of my ways. I’d originally defined the generic Interface with a GUID. Habit, really. However, this meant that both ISubscriber<TSomeEvent> and ISubscriber<TSomeOtherEvent>, and indeed any other interface I defined off this generic interface, would have the same GUID. This combined with the fact that I was using an “as” cast to get the Interface reference from the TMySubscribingObject instance, confused Delphi into always giving me the same Interface reference.
Drop the GUID and drop the “as”, and it all worked beautifully.
In future posts I’ll show the other side of the equation, the Publisher, as well as the Event Broker in the middle. One other nice side-effect of indicating the events a class is interested in, using interfaces in this way, is that the event broker can simply inspect the interfaces implemented by a subscriber to know which events they are interested in subscribing to.