Before going into details about how you could refactor a switch statement first I should say that there's nothing wrong with this statement (as with any other C#, VB.NET or whatever statement). Most probably you would want to refactor a switch in one of these scenarios:

  • It has reached a certain amount of lines of code so that you need to scroll in order to see all the cases.
  • When you want to introduce some new functionality you inevitably end up adding new cases to the switch statement.
  • When you implicitly check for a type in a switch statement. In most cases this is a clear signal that you could take advantage of a more let's say... object oriented approach.

 

One possible way to refactor a switch statement is by using the Derick Bailey's approach and use the "Descriptive State Enumeration" approach. What Derick manages to do is turn this code:

bool shouldShowOrder;  
switch (order.Status)  
{  
    case OrderStatus.Totaled:  
    {  
        shouldShowOrder = true;  
        break;  
    }  
    case OrderStatus.Tendered:  
    {  
        shouldShowOrder = true;  
        break;  
    }  
    default:  
    {  
        shouldShowOrder = false;  
    }  
}  
 
if (shouldShowOrder)  
    ShowTheOrderForFulfillment(order); 

 

into something like this:

public abstract class OrderStatus  
{  
    public static OrderStatus InProcess = new InProcessStatus();  
    public static OrderStatus Totaled = new TotaledStatus();  
    public static OrderStatus Tendered = new TenderedStatus();  
      
    public abstract bool DisplayForFullfillment  
    {  
        get;  
    }  
      
    private OrderStatus()  
    {  
    }  
}  
 
public class InProcessStatus : OrderStatus  
{  
    public bool DisplayForFullfillment  
    {  
        get 
        {  
            return false;  
        }  
    }  
}  
 
public class TotaledStatus : OrderStatus  
{  
    public bool DisplayForFullfillment  
    {  
        get 
        {  
            return true;  
        }  
    }  
}  
 
public class TenderedStatus : OrderStatus  
{  
    public bool DisplayForFullfillment  
    {  
        get 
        {  
            return true;  
        }  
    }  
}  
 
public class Order  
{  
    public OrderStatus Status  
    {  
        get;  
        set;  
    }  
}  
 
double total = order.GetTotal();  
order.Status = OrderStatus.Totaled; 

 

In short what we do here (actually what Derick suggests) is to wrap each case into a new state and delegate some responsibility to each state rather than doing everything in the switch statement in strongly procedural code. This way when you need to introduce a new behavior you won't need to modify the existing code but rather create a new state.

If for some reason you don't want to delegate responsibility to each state and if it turns out that you'll need too many states you could consider using Chris Brandsma's approach and use a Dictionary. What Chris does is turning this code:

public string ReturnSomething(string name, string value)  
{  
    switch (name)  
    {  
        case "Chris":  
            return _valueProcessor.Chris(value);  
              
        case "David":  
            return _valueProcessor.David(value);  
              
        case "Scott":  
            return _valueProcessor.Scott(value);  
          
        case "Jason":  
            return _valueProcessor.Jason(value);  
          
        case "Tony":  
            return _valueProcessor.Tony(value);  
              
        default:  
            return string.Empty;  
    }  

 

into:

public Dictionary<string, Func<stringstring>> CreateDictionary()  
{  
    var dictionary = new Dictionary<string, Func<stringstring>>  
    {  
        {"Chris", _valueProcessor.Chris},  
        {"David", _valueProcessor.David},  
        {"Jason", _valueProcessor.Jason},  
        {"Scott", _valueProcessor.Scott},  
        {"Tony", _valueProcessor.Tony}  
    };  
      
    return dictionary;  
}  
 
public string ReturnSomething(string name, string value)  
{  
    if (_dictionary.ContainsKey(name))  
        return _dictionary[name].Invoke(value);  
 
    return string.Empty;  

 

Some of you may not like this kind of refactoring but nevertheless if it helps you have cleaner code at the end, why not?

In some cases you may have a sequence of several operations where each operation relies on the result or the completion of the previous operation. In such scenarios you could take advantage of what Joel Ross suggests, so assuming you have the following lines of code:

public void DoColorWork(ColorObject color, ColorManager manager)  
{  
    switch(color.PrimaryColor)  
    {  
        case PrimaryColor.Red:  
            manager.OperationOneRed(color);  
            break;  
              
        case PrimaryColor.Green:  
            manager.OperationOneGreen(color);  
            break;  
          
        case PrimaryColor.Blue:  
            manager.OperationOneBlue(color);  
            break;  
    }  
      
    manager.OperationTwo(color);  
      
    switch(color.PrimaryColor)  
    {  
        case PrimaryColor.Red:  
            manager.OperationThreeRed(color);  
            break;  
          
        case PrimaryColor.Green:  
            manager.OperationThreeGreen(color);  
            break;  
          
        case PrimaryColor.Blue:  
            manager.OperationThreeBlue(color);  
            break;  
    }  

 

using the Strategy pattern you could refactor them to:

public void DoColorWork(ColorObject color)  
{  
    ColorManager manager = GetColorManager(color);  
    manager.OperationOne(color);  
    manager.OperationTwo(color);  
    manager.OperationThree(color);  
}  
 
public ColorManager GetColorManager(ColorObject color)  
{  
    switch(color.PrimaryColor)  
    {  
        case PrimaryColor.Red:  
            return new RedColorManager();  
          
        case PrimaryColor.Green:  
            return new GreenColorManager();  
          
        case PrimaryColor.Blue:  
            return new BlueColorManager();  
    }  

 

Instead of the Strategy pattern you could try using the Visitor pattern which is also a good option when you feel tempted to implicitly switch by a type's name (example taken from here).
e.g.

public void SortEmployee(Employee employee)  
{  
    string employeeType = typeof(Employee).Name;  
    switch (employeeType)  
    {  
        case "Engineer":  
            // Cast employee to Engineer and do something  
            break;  
 
        case "Salesman":  
            // Cast employee to Salesman and do something  
            break;  
 
        case "Manager":  
            // Cast employee to Manager and do something  
            break;  
    }  

 

Well, the refactored code will result in much more lines of code but at least we'll be safe if we decide to rename some of the classes inheriting from the Employee class. Also we won't need to cast the employee instance to the different subclasses. And last but not least we'll have code which will have more and clearer points of extensibility:

public interface IVisitor  
{  
    void VisitEngineer(Engineer engineer);  
    void VisitSalesman(Salesman salesman);  
    void VisitManager(Manager manager);  
}  
 
public abstract class Employee  
{  
    public abstract void Accept(IVisitor visitor);  
}  
 
public class Engineer : Employee  
{  
    public override void Accept(IVisitor visitor)  
    {  
        visitor.VisitEngineer(this);  
    }  
}  
 
public class Salesman : Employee  
{  
    public override void Accept(IVisitor visitor)  
    {  
        visitor.VisitSalesman(this);  
    }  
}  
 
public class Manager : Employee  
{  
    public override void Accept(IVisitor visitor)  
    {  
        visitor.VisitManager(this);  
    }  
}  
 
public class EmployeeSortVisitor : IVisitor  
{  
 
    public void VisitEngineer(Engineer engineer)  
    {  
    }  
      
    public void VisitSalesman(Salesman salesman)  
    {  
    }  
      
    public void VisitManager(Manager manager)  
    {  
    }  

 

What we should finally do is just instantiate a new EmployeeSortVisitor in the SortEmployee method and call the Accept method of the employee.
e.g.

public void SortEmployee(Employee employee)  
{  
    var employeeVisitor = new EmployeeSortVisitor();  
    employee.Accept(employeeVisitor);     

 

For those of you who find the switch statement syntax kind of limiting you could check Bart De Smet's functional switch approach.
You'll be able to do some pretty cool things, such as:

new Switch<string>(name)   
    .Case(s => s.StartsWith("B"), s =>   
    {   
        Console.WriteLine(s + " starts with B.");   
    }, true)   
          
    .Case(s => s.StartsWith("Ba"), s =>   
    {   
        Console.WriteLine(s + " starts with Ba.");   
    })   
 
    .Default(s =>   
    {   
        Console.WriteLine(s + " starts with who knows what.");   
    }); 

 

Hope this helps :)


About the Author

Hristo Kosev

 

Comments

Comments are disabled in preview mode.