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<string, string>> CreateDictionary() 
    var dictionary = new Dictionary<string, Func<string, string>> 
    
        {"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.