Sunday, 8 April 2012

C# Event Monitor using Reflection

Objective

I needed a component that will hook into any event raised on any objects, to keep track of the number of events raised. Simple, huh?

Hardwired Proof of Concept

My first attempt was in the constructor of the observed object, that registered its events to be handled by a CountEvent method in an EventMonitor object.
It worked:

But it seriously ignored SRP in that the observed object shouldn't care how its events are handled.

Refactor I - The EventHandler Parameter

With SRP in mind, I created a RegisterEvent method to accept the object's event, so that the monitor could add its own handler - there still needed to be knowledge of observer and observed, but that could be handled in a builder object so no harm there. A nice side effect of this is that the method that actually handled the event (CountEvent) could be made private. This is the EventMonitor code:
Lovely. Next, time to implement the new RegisterEvent in the client:
Easy. Now, time to run it:
Hmm. I'm not entirely sure what happened there, but it appears that you can't just go passing events around like that as it doesn't give you access to the Add and Remove handlers that you need to call (using the += and -= syntax). Note to self: Jon Skeet will know.

The ref Keyword is Your Friend

However, a quick addition of the ref keyword seemed to work:


The Builder

Excellent. Next, I wanted to totally remove the responsibility of event registration from the observed object. Enter, the builder object:

You'll notice that the event passed as the argument to RegisterEvent is in an error state. The error that this produced is "The event 'ObservedObject.ObservedEvent' can only appear on the left hand side of += or -= (except when used from within the type 'ObservedObject')"

What a shame. I can't access the event outside of the class itself except to add or remove a handler. I did want to use the add handler, honest, but just not inside the builder. Ok, that clearly wasn't going to work so I changed tack, and added the event handler directly from inside the builder.
It worked

What's That Smell?

Lets revisit the objective of the exercise: I need a component that will hook into any event raised on any objects, to keep track of the number of events raised.
Clearly I didn't have this yet - I'd merely abstracted the construction of one object into a builder class. All good, but a long, long way from any event on any object.

Reflection

As we know, nothing is really safe from prying eyes in the .Net world; every object is available to be decomposed via reflection. And if you can't beat them, join them... I found an article on MSDN, How to: Hook Up a Delegate Using Reflection. This explained how to use reflection to discover an object's events, and crucially how to create a delegate and use it to handle the object's events.
Here's the final code for the event monitor:
Here's an extended ObservedObject, with a standard and custom eventhandler:
And the builder. Note, this just passes the ObservedObject through to the EventMonitor, ensuring that if the ObservedObject changes by adding events, its builder doesn't need to.

GitHub

The final version of the EventMonitor example is available to inspect here on GitHub

No comments:

Post a Comment