Monday, 20 September 2010

refactoring an enum

Ran into an interesting detour today – had to consider how to make the behaviour of a certain plugin dynamically configurable. Unfortunately the behaviour of the plugin was controlled by an enum – the principle reason for making the value set dynamically configurable was to enable us to add new behaviours without recoding the enum, building and deploying.

To start with, clearly there’s been a design decision that values compile-time checking of this configuration, which we’d weaken if we were to actually refactor like this.

public enum ReportType : int
{
 Simple = 1
}

In principle I could do this by refactoring the enum to a class; the only decisions then are how to ensure backward compatibility if we’re not going to refactor all the client code, and how to get at dynamic values.

To ensure backward compatibility I could add either public consts or public properties – I figured the latter more useful, in case I needed actual logic in order to generate numbers for existing enum values.

public class ReportType
{
 public static int Simple { get { return 1; } }
}

Then, to access dynamically configured values I just added a static constructor to populate a lookup table, and a method for clients to use to do the lookup based on a string. The same method could also return the older, statically configured values.

public static class ReportType
{
 private static Dictionary<string, int> _lookup = new Dictionary<string, int>();

 static ReportType()
 {
     _lookup.Add("Simple", 1);
     _lookup.Add("Complicated", 2);     // or configure from somewhere
 }

 public static int Simple { get { return GetByName("Simple"); } }

 public static int GetByName(string reportType)
 {
     if(_lookup.ContainsKey(reportType))
     {
         return _lookup[reportType];
     }
     else
     {
         throw new ArgumentException("Unknown report type requested", reportType);
     }
 }
}

(One of) The question(s) then is -- how do we start handling these new dynamic errors? Will the client be prepared for them?

A friend suggested that I stick with the original enum and work up an extension method to extend the behaviour to include dynamically configured values – similarly to the class refactor above. This is extremely tempting – the extension method could determine what the last value in the enum was at runtime, and dynamically configure itself to avoid already-used indexes.

It’s tempting until you remember that extension methods are applied to instances, not types, so I wouldn’t be able to use it.

2 comments:

Dean Chalk said...

why not use partial classes

public partial class ReportType
{
public static int Simple { get { return 1; } }
}

then another time

public partial class ReportType
{
public static int Complex { get { return 2; } }
}

Tim Barrass said...

Hey Dean -- I could, but as far as I can tell I'd still need to recompile to get the new values from the partial classes.