This post is a response to Ugly Case Statements, where Sunny tackles the problem of running separate code for each element of an enumeration. I’d like to suggest another alternative to handle this.
First, I’m not convinced yet that the dictionary of delegates pattern is a significant improvement over the switch-statement pattern. If the goal is to make the code that calls the action more readable, this could easily be done by refactoring to extract the switch statement into a method. For example, suppose we need to run different setup code depending on the type of document (e.g. invoices, packing slips, purchase orders, etc.). The extracted method might look like:
void Setup(DocumentType t)
Then calling the Setup(type)
method is simpler and easier to trace than the actionMap pattern. It could even use an extension method on DocumentType to look prettier. The complexity is pushed into this Setup method instead of the InitializeActionMap method.
Taking this one step further, one annoyance with both these patterns is that the code for enumerating the possible document types and the code for defining their setup actions are in different places, so it’s easy to add a document type and forget to add its setup code. Here’s another alternative solution. We can set up the action right alongside the enum definition, like:
public enum DocumentType { [Action(SetupPurchaseOrder)] PurchaseOrder, [Action(SetupSalesOrder)] SalesOrder, }
Here we’re using an attribute called ActionAttribute to annotate the enumeration value. Each attribute has a delegate as a parameter; here SetupPurchaseOrder is a method we define to handle the setup.
public class ActionAttribute : System.Attribute { private Action _action; public Action MyAction { get { return _action; } } public ActionAttribute(Action a) { _action = a; } }
Then you have some library code that looks through the attributes for the enum to execute the command:
public static void Setup(DocumentType t) { //Get the ActionAttributes attached to the DocumentType FieldInfo f = typeof(DocumentType).GetField(t.ToString()); var attrs = f.GetCustomAttributes(typeof(ActionAttribute), true) as ActionAttribute[]; //Execute the action if (attrs.Length > 0) { attrs[0].MyAction(); } }
This approach nicely ties together the document types and the actions required for it. If you have multiple switch statements for the same enumeration, then you can extend this pattern by associating a class with each enumeration value rather than an action. If the action needs to be decoupled from the enumeration, e.g. the user interface needs to be set up, then the SetupPurchaseOrder methods could handle this, e.g. by instantiating the interface from an Abstract Factory.