tags:

views:

178

answers:

2

I have object XML serialized messages coming into a class called MessageRouter. The XML contains the Type name it it was serialized from, and I need to be able to invoke different delegate methods depending on the type that are not known until runtime. I'm not extremely strong in generics so hopefully this will make sense to someone...

I'd like MessageRouter to provide a RegisterDelegateForType method like so:

myMessageRouter.RegisterDelegateForType(new Action<MySerializableType>(myActionHandler));

And then store the types, or the type's string representation in a Dictionary like this:

Dictionary<Type, Action<T>> registeredDelegates;

That way, I can do something like the following pseudocode, calling the type's assigned delegate and passing the deserialized object:

Type xmlSerializedType = TypeFromXmlString(incomingXml);
object deserializedObject = DeserializeObjectFromXml(xmlSerializedType, incomingXml);

// then invoke the action and pass in the deserialized object
registeredDelegates[xmlSerializedType](deserializedObject);

So my questions are:

  1. How do you define a Dictionary that can contain a Type as a key and a generic Action<T> as a value, and have the RegisterDelegateForType method populate the dictionary?
  2. If that's not possible, what's the best way to do this?
+6  A: 

You cannot do this as described, for quite obvious reasons - even if somehow allowed, the last line of code in your example (the one which retrieves a delegate and then calls it) would be non-typesafe, as you're calling an Action<T> - which expects T as an argument - and yet passing it deserializedObject, which is of type object. It wouldn't work in plain code without a cast, why would you expect to be able to circumvent the type check for your case?

In the simplest case, you can do something like this:

Dictionary<Type, Delegate> registeredDelegates;
...
registeredDelegates[xmlSerializedType].DynamicInvoke(deserializedObject);

Of course this will allow someone to add a delegate which takes more or less than one argument to the dictionary, and you'll only find out at DynamicInvoke call, at run-time. But there isn't really any way to define a type which says "any delegate, but with 1 argument only". A better option might be this:

Dictionary<Type, Action<object>> registeredDelegates

and then registering types like this:

myMessageRouter.RegisterDelegateForType<MySerializableType>(
   o => myActionHandler((MySerializableType)o)
);

The above snippet uses C# 3.0 lambdas, but you can do the same - if slightly more verbose - with C# 2.0 anonymous delegates. Now you don't need to use DynamicInvoke - the lambda itself will do the proper cast.

Finally, you can encapsulate the lambda creation into RegisterDelegateForType itself by making it generic. For example:

private Dictionary<Type, Action<object>> registeredDelegates;

void RegisterDelegateForType<T>(Action<T> d)
{
    registeredDelegates.Add(typeof(T), o => d((T)o));
}

And now the callers can just do:

RegisterDelegateForType<MySerializableType>(myHandler)

So it's completely typesafe for your clients. Of course, you're still responsible for doing it right (i.e. passing an object of the correct type to the delegate you retrieve from the dictionary).

Pavel Minaev
+1  A: 

I am not sure that this completely answers your question, but here is a class I wrote that will accomplish what you want. I couldn't tell if you want your Action delegate to take a typed object or not, but in your pseudo code, you pass it an "object" to deserialize so I wrote my class accordingly and it therefore does not use generics:

public delegate void Action(object o);

public class DelegateDictionary {
    private IDictionary _dictionary = new Hashtable();

    public void Register<T>(Action action) {
     _dictionary[typeof(T)] = action;
    }

    public Action Get<T>() {
     return (Action)_dictionary[typeof(T)];
    }

    public static void MyFunc(object o) {
     Console.WriteLine(o.ToString());
    }

    public static void Run() {
     var dictionary = new DelegateDictionary();
     dictionary.Register<string>(MyFunc);
     // Can be converted to an indexer so that you can use []'s
     var stringDelegate = dictionary.Get<string>();
     stringDelegate("Hello World");
    }
}

I believe this will accomplish what you want.

Why not make the IDictionary a Dictionary<Type, Action>?
thecoop
I suppose I was waiting to see if routeNpingme would need to support a delegate type of void Action<T>(T t); If so, you couldn't use Dictionary<Type, Action<T>> and this design could easily be modified to support that. If not, then yes, a Dictionary<Type, Action> would be best. Even if he needs to support Action<T>, the design would be better still if I had used Dictionary<Type, object>.