Click here to Skip to main content
15,867,885 members
Articles / Programming Languages / C#

Reducing Code Complexity on Switch-blocks

Rate me:
Please Sign up or sign in to vote.
4.82/5 (54 votes)
7 Oct 2008CPOL2 min read 112.7K   96   36
Using a Dictionary instead of a switch-case construction to reduce complexity and increase testability

Introduction

A switch-block becomes complex very easily. All code is placed in one method and parts of it are used multiple times. In a lot of cases, a switch-block is based on an enum. Next to that, it's not possible to use a fall-through in C#. The only way to go from case to case is the use of the goto statement, which isn't a good idea when reducing complexity. Although I've written the examples in C#, it wouldn't be hard to apply the principles to other languages.

A Solution

A nice solution to get rid of large switch constructions is the use of a Dictionary<enum,delegate>. This way every element of the enum can be attached to a method. It is possible to call the right method for every enum possible. Because every enum has its own method, it's very easy to use this construction in a testdriven environment. Another nice thing is that it's no problem to call one method from another. Let me explain a little further with an example and some pseudo code. Imagine a program that prepares food. This part contains the recipes.

Let us start with the following enum:

C#
Enum food{
    Apple,
    ApplePie,
    AppleJuice,
    Pizza
}

It's not hard to imagine that all of the foods need a specific preparation, but that some actions need to be done for different foods, like peeling an apple or baking in the oven.

To add the preparations to the Dictionary<enum, delegate>, we first need to define a delegate method:

C#
delegate bool Preperation();

Now we need to define the actual preparation methods for every item of the enum, making sure they're declared the same way as the delegate, thus the same parameters and return value. The method returns a boolean when preparation is successful.

In this example, the methods may look something like this:

C#
bool PeelApple()
{
	// code to remove peel
	return true;
}
bool BakePie()
{
	PreheatOven(180.0);
	PeelApple();
	CreatePie();
	while(!doneBaking())
		Bake();
	return true;
}
bool MakeAppleJuice()
{
	PeelApple();
	Juicify();
	return true;
}
bool BakePizza()
{
	PreheatOven(160.0);
	CreatePizza();
	while(!doneBaking())
		Bake();
	return true;
}

Notice that BakePie() and MakeAppleJuice() both call the method PeelApple(). This is not possible in a switch – case constructor, unless you call the methods from each case.

Now all that's left is to create and initialize the Dictionary.

C#
Dictionary<food, /> FoodPreperation;
..
FoodPreperation = new Dictionary<food, Preperation>();
FoodPreperation.add(food.Apple, new Preperation(PeelApple));
FoodPreperation.add(food.ApplePie, new Preperation(BakePie));
FoodPreperation.add(food.AppleJuice, new Preperation(MakeAppleJuice));
FoodPreperation.add(food.Pizza, new Preperation(BakePizza));

Calling the methods is done by:

C#
food FoodOfChoice = food.ApplePie;
FoodPreperation[FoodOfChoice]();

In the last code snippet, the method that goes with food.ApplePie is executed.

History

  • 07 Oct 2008 - Initial upload

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Velicus B.V.
Netherlands Netherlands
Microsoft MVP Client Dev . Founder of http://StoreAppsUG.nl, the Dutch Windows Store apps and Windows Phone apps usergroup. XAML / HTML5 developer. Writer. Composer. Musician.

Twitter
@Sorskoot

Awards / Honers
• October 2010,2011,2012,2013: Awarded Microsoft Expression Blend MVP
• June 2009: Second Place in the WinPHP challenge
• February 2009: Runner-up in de Mix09 10k Challenge
• June 2008: Winner of the Microsoft expression development contest at www.dekickoff.nl

Bio
I started programming around 1992, when my father had bought our first home computer. I used GWBasic at that time. After using QBasic and Pascal for a few years I started to learn C/C++ in 1996. I went to the ICT Academy in 1997 and finnished it in 2002. Until December 2007 I worked as a 3D specialist. Besides modelling I worked on different development projects like a 3D based Scheduler and different simultion tools in C# and Java. Though out the years I've gained much experience with ASP.NET, Silverlight, Windows Phone and WinRT.

Comments and Discussions

 
GeneralGood One Pin
Sneha B Patel14-Oct-08 23:30
Sneha B Patel14-Oct-08 23:30 
GeneralI've done the same kind of thing but used reflection to build the dictionary... Pin
Member 21876788-Oct-08 15:56
Member 21876788-Oct-08 15:56 
GeneralRe: I've done the same kind of thing but used reflection to build the dictionary... Pin
Timmy Kokke8-Oct-08 20:27
Timmy Kokke8-Oct-08 20:27 
GeneralRe: I've done the same kind of thing but used reflection to build the dictionary... Pin
Member 218767810-Oct-08 13:36
Member 218767810-Oct-08 13:36 
The following code is from my project on CodePlex at http://www.codeplex.com/JSLRefactorExplorer. It's in the MatchMethodLookup.cs file. I will probably be cleaning and refactoring it sometime in the future but right now I've got other things to do. All in all the codes pretty simple if you understand reflection, Linq and Linq expressions. The project is in its very early stages. I'm planning on making big changes which will change this code slightly.

The objective of the code is to build a dictionary with a Type as the key and a delegate as the value. The Type that I'm passing in has a bunch of static methods starting who's names start with "Match" and take three parameters. The important parameters are the second and third ones. They have to be the same and derive off of CodeUnit or CsElement. All that is the WHERE part of the Linq query in the BuildLookupTable(Type type) method.

The return statement takes the Linq query and builds a dictionary out of it. That's about it for that method.

The CreateDelegate method takes the MethodInfo found in the above method and creates a delegate that's defined in another file in the project:
public delegate void MatcherMethod(MatchState state, ICodeUnit left, ICodeUnit right);

The method builds the three parameters for the delegate, adds a single statement which is a method call and compiles the expression. The second and third parameter types of the methods being called implement ICodeUnit. For example AssignementStatement is an Abstract Syntax Tree element that implements ICodeUnit. Since the methods being called by my Linq expression are "more specific", the Linq expression has to take in two ICodeUnit's and convert them to the appropriate concrete types. Your example was working with enums so you wouldn't have to do this step.

So to wrap things up... The performance hit from the reflection and the Linq compiling is very small. Best of all, I only have to add a new "Match" method to the appropriate class and it automatically gets use! There's no need to add code to add a new item to a dictionary, etc.

Here's the code from my CodePlex project. It will probably change in the next couple days but the process will be similar.

namespace Jsl.RefactoringExplorer
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using Microsoft.StyleCop.CSharp;
    using Expression = System.Linq.Expressions.Expression;  // Use Linq Expression instead of StyleCop's.

    public static class MatchMethodLookup
    {
        #region Public Methods
        public static IDictionary<type,> BuildLookupTable(Type type)
        {
            // Build a list of all the appropriate methods.
            var methods =
                from method in type.GetMethods(BindingFlags.Public | BindingFlags.Static)
                let parameters = method.GetParameters()
                where method.Name.StartsWith("Match", StringComparison.Ordinal)
                    && parameters.Length == 3
                    && parameters[0].ParameterType == typeof(MatchState)
                    && (parameters[1].ParameterType.IsSubclassOf(typeof(CodeUnit))
                        || parameters[1].ParameterType.IsSubclassOf(typeof(CsElement)))
                    && parameters[2].ParameterType == parameters[1].ParameterType
                select new { method, parameters[1].ParameterType };

            return methods.ToDictionary(
                methodInfo => methodInfo.ParameterType,
                methodInfo => MatchMethodLookup.CreateDelegate(methodInfo.method, methodInfo.ParameterType));
        }
        #endregion Public Methods

        #region Private Methods
        private static MatchState.MatcherMethod CreateDelegate(MethodInfo method, Type parameterType)
        {
            var matchStateParameter = Expression.Parameter(typeof(MatchState), "state");
            var leftParameter = Expression.Parameter(typeof(ICodeUnit), "left");
            var rightParameter = Expression.Parameter(typeof(ICodeUnit), "right");

            var leftAsSpecific = Expression.Convert(leftParameter, parameterType);
            var rightAsSpecific = Expression.Convert(rightParameter, parameterType);

            var methodCallExpression = Expression.Call(method, matchStateParameter, leftAsSpecific, rightAsSpecific);

            return Expression.Lambda<matchstate.matchermethod>(
                methodCallExpression,
                matchStateParameter,
                leftParameter,
                rightParameter).Compile();
        }
        #endregion Private Methods
    }
}</matchstate.matchermethod>

GeneralRe: I've done the same kind of thing but used reflection to build the dictionary... Pin
Member 218767814-Oct-08 13:31
Member 218767814-Oct-08 13:31 
GeneralI've done this. Pin
Jamie Nordmeyer8-Oct-08 4:30
Jamie Nordmeyer8-Oct-08 4:30 
GeneralRe: I've done this. Pin
Timmy Kokke8-Oct-08 20:23
Timmy Kokke8-Oct-08 20:23 
GeneralRe: I've done this. Pin
Jamie Nordmeyer9-Oct-08 4:54
Jamie Nordmeyer9-Oct-08 4:54 
GeneralRe: I've done this. Pin
Palisade12-Jul-11 10:19
Palisade12-Jul-11 10:19 
GeneralNice idea Pin
Leeland Clay8-Oct-08 3:05
Leeland Clay8-Oct-08 3:05 
GeneralA design pattern alternative Pin
ervegter8-Oct-08 2:10
ervegter8-Oct-08 2:10 
GeneralRe: A design pattern alternative Pin
Timmy Kokke8-Oct-08 2:16
Timmy Kokke8-Oct-08 2:16 
GeneralRe: A design pattern alternative Pin
zenwalker19851-Oct-11 18:18
zenwalker19851-Oct-11 18:18 
GeneralI like the idea. Pin
aamironline8-Oct-08 1:38
aamironline8-Oct-08 1:38 
GeneralDon't need the delegate [modified] Pin
Casual Jim8-Oct-08 1:08
Casual Jim8-Oct-08 1:08 
GeneralRe: Don't need the delegate Pin
Timmy Kokke8-Oct-08 1:42
Timmy Kokke8-Oct-08 1:42 
GeneralTyping error Pin
Roberto Italy7-Oct-08 20:22
Roberto Italy7-Oct-08 20:22 
GeneralRe: Typing error Pin
Danny Ruijters24-Jan-09 5:23
Danny Ruijters24-Jan-09 5:23 
JokeRe: Typing error Pin
Timmy Kokke24-Jan-09 5:40
Timmy Kokke24-Jan-09 5:40 
GeneralAhh delegate. Pin
Giovanni Bejarasco7-Oct-08 18:14
Giovanni Bejarasco7-Oct-08 18:14 
GeneralYeah, a good idea Pin
PIEBALDconsult7-Oct-08 15:53
mvePIEBALDconsult7-Oct-08 15:53 
GeneralNice Idea! Pin
dwilliss7-Oct-08 15:28
dwilliss7-Oct-08 15:28 
GeneralRe: Nice Idea! Pin
Timmy Kokke7-Oct-08 20:15
Timmy Kokke7-Oct-08 20:15 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.