Self-registering methods


Several times now, I have found myself writing code like this

public int Parse(string id, LetterInfo parms)
{
    switch (id)
    {
        case "a":
            result = MyLibrary.DoAlphaProcessing(parms);
            break;
        case "b":
            result = MyLibrary.DoBetaProcessing(parms);
            break;
        case "c":
            result = MyLibrary.DoGammaProcessing(parms);
        ...
    }

    return result;

typically for a factory or some parsing function.

If the number of cases is small, then this is usually not an issue, but when there are a large number of cases, or when the code is still evolving and the code for each case is in flux, then this kind of code is cumbersome and a little ugly (try looking at a 5-screen long switch statement).

The technique I use to get around this is to make the class contain “self-registering” methods. I do this with the following steps:

  1. Create an Attribute class to identify the various methods to be called and decorate them.
  2. In a static constructor, find all the methods decorated with the Attribute and, for each, map an id to an anonymous call to the method.
  3. Change the calling code to find the mapping based on the id and execute the mapped function call.

Now assume the following:

  • MyLibrary is the name of class in which all the target functions reside.
  • idType refers to the type of the object used in the switch statement above.
  • ProcessMethod refers to one of the methods called in the switch statement (e.g. DoAlphaProcessing(), or DoBetaProcessing(), etc.)
  • resultType refers to the type of the object returned by a call to a ProcessMethod.
  • parmType refers to the type(s) of the object(s) passed to a ProcessMethod.

So the steps look something like this
1) Create an Attribute class to identify the various methods to be called and decorate them.

private class MyAttrClass : Attribute
{
    public MyAttrClass(idType id)
    {
        Id = id;
    }
    public idType Id { get; set; }
}

and

[MyAttrClass(idValue)]
private resultType ProcessMethod(parmType parms) {...}

e.g.

private class LetterMethod : Attribute
{
    public LetterMethod(string letter)
    {
        LetterId = id;
    }
    public string LetterId { get; set; }
}

and

[LetterMethod("a")]
private resultType DoAlphaProcessing(LetterInfo parms) {...}

[LetterMethod("b")]
private resultType DoBetaProcessing(LetterInfo parms) {...}

[LetterMethod("c")]
private resultType DoGammaProcessing(LetterInfo parms) {...}

2) In a static constructor, find all the methods decorated with the Attribute and, for each, map an id to an anonymous call to the method.

public static class MyLibrary
{
  private static Dictionary _functionMappings;

  static MyLibrary()
  {
    _functionMappings = new Dictionary();

    var methods =
        typeof(MyClass)
        .GetMethods(System.Reflection.BindingFlags.Static |
                    System.Reflection.BindingFlags.NonPublic)
        .Where(m => m.IsDefined(typeof(MyAttrClass), false));

    foreach(var method in methods)
    {
       var id = 
           ((MyAttrClass)(method.GetCustomAttributes(typeof(MyAttrClass), false)
            .FirstOrDefault()))
           .Id;
       _functionMappings
           .Add(id, (p) => (resultType)method.Invoke(null, new object[] { p })); 
    }
  } 
}

or if MyLibrary is not static, then remove the reference to System.Reflection.BindingFlags.Static above and change the last statement to

        _functionMappings
            .Add(id, (o, p) => (resultType)method.Invoke(o, new object[] { p }));

e.g.

public class MyLibrary
{
  private static Dictionary _functionMappings;

  static MyClass()
  {
    _functionMappings = new Dictionary();

    var methods =
        typeof(MyClass)
        .GetMethods(System.Reflection.BindingFlags.NonPublic)
        .Where(m => m.IsDefined(typeof(LetterMethod), false));

    foreach(var method in methods)
    {
        var id = 
            ((LetterMethod)(method.GetCustomAttributes(typeof(LetterMethod), false)
            .FirstOrDefault()))
            .LetterId;
        _functionMappings
            .Add(id, (o, p) => (int)method.Invoke(o, new object[] { p }));
    }
  }
}

3) Change the calling code to find the mapping based on the id and execute the mapped function call.

if (_functionMappings.ContainsKey(id))
{
    var result = _functionMappings[id](parms);

or if MyLibrary is not static

    var result = _functionMappings[id](this, parms);
}

e.g.

public int Parse(string id, LetterInfo parms)
{
    if (_functionMappings.ContainsKey(id))
    {
        var result = _functionMappings[id](parms);
    }
    ...
    return result;
}

Now if we need to add a new method, there is no switch to update. Just add the new method to the MyLibrary class,  decorated with the Attribute:

[FunctionMethod("d")]
private int DoDeltaProcessing(LetterInfo parms) {...}

The new method will automatically be registered in the function mappings and when the Parse() method is called with an id of “d”, the code will automatically execute DoDeltaProcessing().

var parseResult = MyLibrary.Parse(d, someLetterInfoData);

(the d in the call above should be quoted, but WordPress is dropping text when I do that).

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s