Click here to Skip to main content
Click here to Skip to main content

Reducing Code Complexity on Switch-blocks

By , 7 Oct 2008
 

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:

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:

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:

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.

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:

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)

About the Author

Timmy Kokke
Software Developer (Senior) Centric Netherlands B.V.
Netherlands Netherlands
Member
MVP Expression Blend. Founder of Sixin.nl the dutch Silverlight and Expression Usergroup. Silverlight developer. Writer. Composer. Musician.
 
Twitter
@Sorskoot
 
Awards / Honers
• October 2010,2011,2012: 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.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralThats a Good Solution !memberSamer Abu Rabie24 Oct '08 - 11:42 
Really you provided a good solution to reduce complexity, but I think one of the good benifits of it is the idea of using objects in the switch statement instead of integers and enums, this way we can use any objects we want.
 
Sincerely Samer Abu Rabie
 
Note: Please remember to rate this post to help others whom reading it.


GeneralGood OnememberSneha B Patel14 Oct '08 - 23:30 
Big Grin | :-D Hi, Timmy
You've made my day 2d easy for what i want.
Good article.
GeneralI've done the same kind of thing but used reflection to build the dictionary...memberMember 21876788 Oct '08 - 15:56 
I was writing a program to match code in abstract syntax trees. Basically parsed C# code. This leads to a bunch of small methods that specifically match one statement/expression to another. For example two assignment statements. I made a static class that had a method for each statement/expression type and used reflection to build the dictionary that cross referenced the statement/expression type to the method that dealt with it. My methods looked like:
public static void MatchAssignmentStatement(AssignmentStatement left, AssignmentStatement right) {...}
I then used Linq.Expression to build and compile a delegate appropriate for each. This gets you away from having to hard code building the dictionary.
 
Another different but similar thing I've done is use the text templating engine built into Visual Studio 2008. These are files with the .tt extention. I would use reflection in the template itself to build a C# class. In your case, you could use the enum to build the code to populate your dictionary.
 
OOPS, the wife is home. Let me know if you are interested...
GeneralRe: I've done the same kind of thing but used reflection to build the dictionary...memberTimmy Kokke8 Oct '08 - 20:27 
Great ideas! Could you send me an example of your code? I'm very curious how you did this.
 
Dawn is nature's way of telling you to go to bed.

GeneralRe: I've done the same kind of thing but used reflection to build the dictionary...memberMember 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...memberMember 218767814 Oct '08 - 13:31 
Scott Hanselman just did a blog on using the T4 templating engine that comes in Visual Studio 2008:
http://www.hanselman.com/blog/T4TextTemplateTransformationToolkitCodeGenerationBestKeptVisualStudioSecret.aspx[^]
 
The examples he lists are probably better than I can do.
GeneralI've done this.memberJamie Nordmeyer8 Oct '08 - 4:30 
I've actually done this in a couple of production apps, and it worked great! Much easier to read. I'd be interested in running a test case at some point to see how this method compares to a switch statement in the generated IL, though.
 
Kyosa Jamie Nordmeyer - Taekwondo Yi (2nd) Dan
Portland, Oregon, USA

GeneralRe: I've done this.memberTimmy Kokke8 Oct '08 - 20:23 
That's what I thought too. Are you using unit tests too? If you test this constuction I'd be very much interested in the results. I do not have time to run a test case in the next few weeks, maybe after that.
 
Dawn is nature's way of telling you to go to bed.

GeneralRe: I've done this.memberJamie Nordmeyer9 Oct '08 - 4:54 
So, it looks like the switch statement produces about the same amount of IL. The dictionary method is still nice, as it allows for dynamic control of the "switching" at run time. I used the following code:
using System;
using System.Collections.Generic;
 
namespace App
{
	public enum Color
	{
		Red,
		Blue,
		Yellow
	}
	
	public class Tester
	{
		private delegate void Method();
		private static Dictionary<Color,Method> _delegates;
		
		public static void UseSwitch(Color color)
		{
			switch (color)
			{
				case Color.Red:
					Console.WriteLine("Eat a raspberry");
					break;
				case Color.Blue:
					Console.WriteLine("Eat a blueberry");
					break;
				case Color.Yellow:
					Console.WriteLine("Eat a banana");
					break;
			}
		}
		
		public static void UseDictionary(Color color)
		{
			_delegates[color]();
		}
		
		public static void UseRed()
		{
			Console.WriteLine("Eat a raspberry");
		}
		
		public static void UseBlue()
		{
			Console.WriteLine("Eat a blueberry");
		}
		
		public static void UseYellow()
		{
			Console.WriteLine("Eat a banana");
		}
		
		public static void Main()
		{
			_delegates = new Dictionary<Color,Method>();
			_delegates.Add(Color.Red, UseRed);
			_delegates.Add(Color.Blue, UseBlue);
			_delegates.Add(Color.Yellow, UseYellow);
			
			UseSwitch(Color.Red);
			UseDictionary(Color.Yellow);
		}
	}
}
 
The IL for the switch statement looks like this (14 lines of IL):
.method public hidebysig static void UseSwitch(valuetype App.Color color) cil managed
{
    .maxstack 1
    .locals init (
        [0] valuetype App.Color color2)
    L_0000: ldarg.0 
    L_0001: stloc.0 
    L_0002: ldloc.0 
    L_0003: switch (L_0015, L_0020, L_002b)
    L_0014: ret 
    L_0015: ldstr "Eat a raspberry"
    L_001a: call void [mscorlib]System.Console::WriteLine(string)
    L_001f: ret 
    L_0020: ldstr "Eat a blueberry"
    L_0025: call void [mscorlib]System.Console::WriteLine(string)
    L_002a: ret 
    L_002b: ldstr "Eat a banana"
    L_0030: call void [mscorlib]System.Console::WriteLine(string)
    L_0035: ret 
}
 
The IL for the dictionary call looks like so :
.method public hidebysig static void UseDictionary(valuetype App.Color color) cil managed
{
    .maxstack 8
    L_0000: ldsfld class [mscorlib]System.Collections.Generic.Dictionary`2 App.Tester::_delegates
    L_0005: ldarg.0 
    L_0006: callvirt instance !1 [mscorlib]System.Collections.Generic.Dictionary`2::get_Item(!0)
    L_000b: callvirt instance void App.Tester/Method::Invoke()
    L_0010: ret 
}
plus an additional 3 lines for each method:
.method public hidebysig static void UseRed() cil managed
{
    .maxstack 8
    L_0000: ldstr "Eat a raspberry"
    L_0005: call void [mscorlib]System.Console::WriteLine(string)
    L_000a: ret 
}
 
My pattern for this has been to use the switch statement when I'm only comparing a few things, but to use a dictionary when I need dynamic control of the structure, or have a lot of conditionals (as a switch statement with 20 cases is incredibly ugly).
 
Kyosa Jamie Nordmeyer - Taekwondo Yi (2nd) Dan
Portland, Oregon, USA

GeneralRe: I've done this.memberPalisade12 Jul '11 - 10:19 
class Program
{
public enum Color
{
Red,
Blue,
Yellow
}
 
static string[] colors = new string[] { "Eat a raspberry", "Eat a blueberry", "Eat a bannana" };
 
public static void Main(string[] args)
{
Console.WriteLine(colors[(int)Color.Blue]);
}
}
 
The main IL assembly looks like this:
 
IL_0000: ldsfld string[] ConsoleApplication1.Program::colors
IL_0005: ldc.i4.1
IL_0006: ldelem.ref
IL_0007: call void [mscorlib]System.Console::WriteLine(string)
IL_000c: ret
 
What do I win?! Cool | :cool:
GeneralNice ideamemberLeeland Clay8 Oct '08 - 3:05 
I like the way you did this. The first thing that came to mind was to create a dynamic "switch" using reflection to pull in methods at design time. I havn't put many brain cells towards (and I haven't had enough coffee to do that yet), but I think might be worth checking out.
GeneralA design pattern alternativememberervegter8 Oct '08 - 2:10 
Hello,
 
I like the elegance of your solution. However there is also another approach to your described problem. It is known as the "Chain of command" design pattern.
http://www.cs.clemson.edu/~malloy/courses/patterns/chain.html
 
The beauty of this design pattern lies in the fact that every class in the chain can decide if it is capable of handling the call or pass it on to it's successor.
 
Cheers.
GeneralRe: A design pattern alternativememberTimmy Kokke8 Oct '08 - 2:16 
Thanks for the tip! I'll look in to it.
 
Dawn is nature's way of telling you to go to bed.

GeneralRe: A design pattern alternativememberzenwalker19851 Oct '11 - 18:18 
But one must also know that Design patterns are not solution to a problem but rather an alternative to solve the problem easily and maintain it further. So if there is a simple solution with out design patterns, i guess it should be taken. 2 much of design pattern would kill us.
GeneralI like the idea.memberM Aamir Maniar8 Oct '08 - 1:38 
Hi Timmy
 
I like the idea you presented here. It looks cool and clean.
 
M Aamir Maniar
aamirOnline.com

GeneralDon't need the delegate [modified]memberCasual Jim8 Oct '08 - 1:08 
Hi
 
You don't need the Preparation delegate but you can just use the intrinsic Func<> object
Dictionary<food,Func<bool>>
 
Also you don't need to use a constructor for delegates from .net 2.0 you can just use a method group
 
public void SomeMethod(){
//Do some work here
}
 
dict.Add(food.Apple, SomeMethod);
 
modified on Wednesday, October 8, 2008 7:26 AM

GeneralRe: Don't need the delegatememberTimmy Kokke8 Oct '08 - 1:42 
I like that idea. I'm planning on doing a follow-up article on this one. It's about adding decisions into this, that one makes use of several Func<bool>.
 
Thanks for the comment!
 
Dawn is nature's way of telling you to go to bed.

GeneralTyping errormemberRoberto Italy7 Oct '08 - 20:22 
These words in your article: "Nu alleen nog de Dictionary maken en initialiseren. " are in dutch Smile | :)
 
Roberto

GeneralRe: Typing errormemberDanny Ruijters24 Jan '09 - 5:23 
Also I doubt whether an "ApplePizza" is very tastefull... Wink | ;)
JokeRe: Typing errormemberTimmy Kokke24 Jan '09 - 5:40 
Don't tell me you never had one?! We eat those all the time.. Laugh | :laugh:
 
I'll change the typo, thanks!
 
Dawn is nature's way of telling you to go to bed.

GeneralAhh delegate.memberGio Bejarasco7 Oct '08 - 18:14 
I'm glad you provide one good usage to this seldom-utilized .NET constuct. It's a nice addition to anyone's tool belt. Thanks. Cinco!
GeneralYeah, a good ideamemberPIEBALDconsult7 Oct '08 - 15:53 
But as long as you have all the code in methods, you've already removed the complexity from the switch, so the primary benefit of this technique is ease of maintenance.
 
Another benefit is the ability to "switch" on non-intrinsic objects, but you didn't mention that.
GeneralNice Idea!memberdwilliss7 Oct '08 - 15:28 
That's a pretty good idea. Never thought of doing it that way.
 
BTW, you've got one sentence in the middle of the article that you forgot to translate to English Smile | :)
GeneralRe: Nice Idea!memberTimmy Kokke7 Oct '08 - 20:15 
Thank you for pointing that out... I'll fix it right away...
 
Dawn is nature's way of telling you to go to bed.

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

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130523.1 | Last Updated 7 Oct 2008
Article Copyright 2008 by Timmy Kokke
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid