Friday, September 08, 2006
I had a little fun today in C# with generics and anonymous delegates. Just wanted to show it off so you can see how cool these things are if you aren't using them yourselves.
 
I have this xml file that defines a template for displaying controls. So when parsing this template I'm reading out values and assigning them to properties on the controls. Parsing out the values involves parsing them from strings into their proper types. So I have a bunch of lines like these:
control.Name = node.Attributes["name"].Value;
control.FontSize = new FontUnit(node.Attributes["fontSize"].Value);
control.IndexType = (IndexType)Enum.Parse(typeof(IndexType), node.Attributes["indexType"].Value);
control.Level = int.Parse(node.Attributes["level"].Value);
control.Flow = long.Parse(node.Attributes["flow"].Value);
I'm not doing a try/catch around every line here, because I can trust my templates to contain working values, so it's all neat and tidy.
 
But now I want to add profile support. I have this profile file that may or may not contain a per-user value for any of these properties. And these profile files I do not trust to be properly formatted. That means that I need to: Check if the property value exists in the profile. If not, use the template value. If it does, parse it to the proper type. If the parsing succeeds, use that value, otherwise fallback to the template value.
 
So in my code these nice one-liners now becomes a bit of a mess:
if (profile.ContainsKey("fontSize"))
{
    try
    {
        control.FontSize = new FontUnit(profile["fontSize"]);
    }
    catch (Exception)
    {
        control.FontSize = new FontUnit(node.Attributes["fontSize"].Value);
    }
}
else
{
    control.FontSize = new FontUnit(node.Attributes["fontSize"].Value);
}
 
Imagine 50 of those copy-pasted underneath each other. Normally, you can't really extract this out into a method because of the differences in the parsing of the values. Some values use int.Parse(), some use new FontUnit(), others use Enum.Parse(). You'd need a separate ParseX method for every type you support.
 
This is where the generics and anonymous delegates come to the rescue. Now we can get away with a single generic method:
private delegate T ValueParser<T>(string s);
private T ParseValue<T>(ControlProfile profile, string profileKey, string templateValue, ValueParser<T> parser)
{
    if (profile.ContainsKey(profileKey))
    {
        try
        {
            return parser(profile[profileKey]);
        }
        catch (Exception)
        {}
    }
    return parser(templateValue);
}
Now my code is back to nice one-liners like this (line-broken for the sake of this blog):
control.Name = node.Attributes["name"].Value;
control.FontSize = ParseValue<FontUnit>(profile, 
   "fontSize", node.Attributes["fontSize"].Value,
   delegate(string s) { return new FontUnit(s); });
control.IndexType = ParseValue<IndexType>(profile, 
   "indexType", node.Attributes["indexType"].Value,
   delegate(string s) { return (IndexType)Enum.Parse(typeof(IndexType), s); });
control.Level = ParseValue<int>(profile, 
   "level", node.Attributes["level"].Value,
   int.Parse);
control.Flow = ParseValue<long>(profile, 
   "flow", node.Attributes["flow"].Value,
   long.Parse);
Notice how int.Parse and long.Parse don't need a delegate(string s){} wrapper, as they in themselves satisfy the signature of the delegate.
 
Neat :)
 
P.S: Stupid that Enum.Parse doesn't have a generic overload that lets you skip that typecasting and typeof business.