I’ve spend some time with the Pub / Sub topic on my own in the past. Although I
still like my own implementation, even a year after I’ve originally
written it (yes, rare but sometimes that happens), I really like how
Jeremy implemented it. The EventAggregator in StoryTeller is one of
those examples of how much you can achieve with only a few lines of
code. So let’s go diving again ;-)
A type who is interested in recieving messages from the EventAggregator
in StoryTeller has to implement the IListener<T> interface where T
specifies the concrete message / event. A container extension is used
for automatic registration of instances at the EventAggregator after
instances have been created in the container (more on this later in the
post).
1 2 3 4 | |
The interface to the EventAggregator looks like this.
1 2 3 4 5 6 7 | |
Most of the interface looks familiar to me, except the
SendMessage<T>(Action<T> action) method. The EventAggregator
implementation in StoryTeller adds a interesting feature to the topic:
Using delegates instead of explicit event classes. One of the things
Jeremy mentioned in his NDC “Presentation Patterns” talk is that
sometimes creating event classes for events felt a bit tedious to him,
especially when those events only have signal character and don’t carry
any data with them around. IIRIC the varation with delegates instead of
event classes implemented in StoryTeller came up in discussion with
Glenn Block and the Prism team. However it didn’t make it into Prism in
the end. See the difference in usage for yourself:
1 2 3 4 | |
Below is the code for the EventAggregator. There are some interesting things to notice.
- First of all, the
EventBrokerdoesn’t know about subscriptions for a particular type. It only knows listener objects. Compatible listeners are found on the fly when the event / message is published by iterating over all known listener objects and calling the CallOn extension method. I’ll spare the code for this extension method because all it does is executing anAction<T>only when an object can be cast to the type specified byT. - Automatic thread-synchronization to the main-thread is applied by
using the
SynchronizationContextclass. Imho, one of the gems in the .NET 2.0 release that more people should be aware of. I think this class is a really great feature for freeing client code from dealing with callback synchronization issues. Just let the framework handle theInvokeRequiredstuff for you. No one likes to write that stuff anyway. - Jeremy likes it functional ;-). I think this is an interesting example how C# 3.0 code can actually differ from an 1.0 implementation. The shown code is really, really dense and focussed by using a lot of the C# 2.0 and 3.0 features like lamda expressions, extension methods and of course generics. Personally I really like this coding style, however a lot of my colleagues don’t. The debugging story differs a lot from the one using classic if and for loops, which isn’t such a problem for the test-first or test-parallel guys, but I can see where this might feel a bit awkward when you’ve relied on the debugger for most of your developing efforts in the past. In my opinion it’s just a matter of personal taste. Just give it a try and make your own opinion …
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | |
As I said earlier in this post, instances are registered after they’ve been
created by the container. The functionality for this is provided by a
TypeInterceptor class. This class is part of the StructureMap API. Once
it has been registered every created instances is passed through this
interceptor after it has been resolved. If the instance is identified as
relevant for the EventAggregator it is automatically registered.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
Conclusion
EventAggregation is one of the standard patterns in modern enterprise applications. I like the elegant way how Jeremy implemented this. However having spend some time with this in the past, I miss a functional part in his implementation: Type based subscriptions. Type based subscriptions can be really handy when you’re implementing PageFlows or state-ful MessageHandlers. Once a message is recieved the type is resolved by the container and the related handler method is called on the newly created type. I had them in my last application and liked them a lot. Maybe StoryTeller goes alternative routes to achive a similar effect. We’ll see as we proceed in the code …