Thursday, January 31, 2008

Reflecting on reflection

Submit this story to DotNetKicks

Some weeks ago I was working on a project where we receive some ten or twenty different message types from a server, and they all need to be handled when they arrive. Of course, we want to use event driven programming to make this happen on the fly, instead of polling all the time.

All of the messages have the header in common, so we already made a abstract parent class called Message, and when it came to handling all the different messages, we wanted to write as little code as possible. So we added an abstract method called HandleResponse to the code.

So the idea was to identify the incoming message type and invoke the HandleResponse using reflection on the object that we had identified this as. To do this, we made a little xml file that contains the message code (A-Z) along with the name and reference to the class linked to the message type. When the program starts, it reads the xml into a dictionary, so we can look up the key (message code) and get the class reference in return.

XDocument descriptorsXml = XDocument.Load( @".\Data\MessageDescriptors.xml");

var descQuery = from desc in descriptorsXml.Descendants("Message")

select new MessageDescriptor
Code = desc.Element("Code").Value,
Name = desc.Element("Name").Value,
ObjectType = desc.Element("ObjectType").Value.ToType() ,
IsServerMessage = Convert.ToBoolean( desc.Element("IsServerMessage").Value )
foreach (MessageDescriptor m in descQuery)
descriptors.Add(m.Code, m);

Then, we have a MessageHandlingFactory that simply gets objects in from a queue, identifies the type, and invokes the HandleResponse. If the object isn't recognized (could be a new message type, or could be that the programmer forgot to insert the description in the XML file), the MessageHandlingFactory can either throw an exception, or better yet, return a string with message that tells the user what went wrong. If everything goes as planned, it returns a string with the identified typename.

object theType = Activator.CreateInstance(t, SessionID);
EventInfo eInfo = t.GetEvent("OnWriteEvent");
Message.WriteEventHandler theHandler = new Message.WriteEventHandler(OnWriteEventHandler);

eInfo.AddEventHandler(theType, theHandler);
theType.GetType().GetMethod("HandleResponse").Invoke(theType, new object[] { message });

return "MessageHandler: " + messageType;

That's all, folks!

Update February 13, 2008: The code has now been tested in a very high throughput environment, and it performs flawlessly! There's no indication of latency even after invoking some millions of objects. And with that amount of data, everything counts. I'll not vouch for the objects that you choose to invoke, though ;)

No comments: