Tuesday, September 15 2009
One language feature that I'm looking forward to in C# 4.0 is support for covariance and contravariance of generics types. The definitive series of posts on these language features for C# can be found on Eric Lippert's blog and I would invite you to go read there first before if you haven't got the slightest clue what I'm talking about. My outlook on these features wasn't always so sunny though; it was only recently that I ran into a situation where I really wish that I had them. Luckily, I was able to find a small nuance that allowed me to work around covariance limitations in a small subset of problems: covariance of parameters to delegate types.
The particular problem that I was mucking around with had to do with dynamic dispatch. This simply involves registering a key to type generic callback function (Func) in a Dictionary for later use. The important nuance here is that in each place that I register, I want the type that is passed into the callback to be strongly typed (i.e. I don't want to have to downcast in the function I pass as a parameter). So in this kind of Dictionary:
/// <summary>
/// The lookup registry for dispatching operations by name and type
/// </summary>
private Dictionary<Type, Dictionary<string, DispatchRegistration<object>>> dispatchRegistry = new Dictionary<Type, Dictionary<string, DispatchRegistration<object>>>();
As always, all of this code is available under the MIT license on
GitHub.
Where our DispatchRegistration class simple looks like this:
/// <summary>
/// Stores information about a particular dispatch action
/// </summary>
/// <typeparam name="T">The type of the payload of the dispatch action.</typeparam>
public class DispatchRegistration<T>
{
/// <summary>
/// Initializes a new instance of the <see cref="DispatchRegistration<T>"/> class.
/// </summary>
public DispatchRegistration()
{
}
/// <summary>
/// Gets or sets the name of the dispatch action.
/// </summary>
/// <value>The name of the dispatch action.</value>
public string Name
{
get;
set;
}
/// <summary>
/// Gets or sets the dispatch action.
/// </summary>
/// <value>The dispatch action.</value>
public Func<T, DispatchResult> DispatchAction
{
get;
set;
}
}
Type safety can seem a little difficult. Registering one of these with our DynamicDispatcher is relatively easy though. First we have to define some Registration function:
/// <summary>
/// Registers the specified name with the specificed dispatch action.
/// </summary>
/// <typeparam name="T">The type of the payload of the dispatch action.</typeparam>
/// <param name="name">The name to register.</param>
/// <param name="dispatchAction">The dispatch action.</param>
/// <returns>The registration now associated with name.</returns>
public DispatchRegistration<T> Register<T>(string name, Func<T, DispatchResult> dispatchAction)
Then we simply check to see if we've encountered the type before and create a registry for keys taking that type as parameter to the callback:
// Check to see if we've encountered typeof(T) before
Dictionary<string, DispatchRegistration<object>> typeOfTRegistry;
if (!this.dispatchRegistry.TryGetValue(typeof(T), out typeOfTRegistry))
{
typeOfTRegistry = new Dictionary<string, DispatchRegistration<object>>();
this.dispatchRegistry[typeof(T)] = typeOfTRegistry;
}
And then we just create a strongly typed DispatchRegistration<T>:
DispatchRegistration<T> registration = new DispatchRegistration<T>()
{
Name = name,
DispatchAction = dispatchAction
};
typeOfTRegistry[name] = registration as DispatchRegistration<object>;
return registration;
Right? Well, not quite. Unfortunately, this is where covariance starts to get in the way. We can't actually upcast the DispatchRegistration<T> to a DispatchRegistration<object> because of covariance limitations. It is said to be covariant operation because although it manipulates the type T it preserves the bigness and smallness relationship between the two types. So what are we to do now that we know the upcast won't work? Well we do know the type T when we are creating the DispatchRegistration, so we can actually statically downcast to T when the type T is encountered:
DispatchRegistration<object> registration = new DispatchRegistration<object>()
{
Name = name,
DispatchAction = obj => dispatchAction((T)obj)
};
typeOfTRegistry[name] = registration;
return registration;
The lambda expression is the key to this whole operation. Now I'm not going to delve into why this is type safe, but I'm relatively sure that it is because both the Register and Dispatch operations of the DynamicDispatcher are strongly typed; you can only register with delegates that take parameters that are typeof(T) and you can only dispatch with parameters that are also typeof(T). Of course now our method signature is somewhat less elegant, but it's definitely better than the alternative!
public DispatchRegistration<object> Register<T>(string name, Func<T, DispatchResult> dispatchAction)
I'm interested to hear what people have to say about this approach. Of course, all of the code is available on GitHub.