The
switch statement sucks. I firmly believe that the only reason it is still haunting C-style languages these days is that it has been with us since ancient times. The
switch statement has limited usefulness, remarkably heavy syntax and is likely to produce errors.
The Switch Statement Can Easily Be Replaced by If Statements
The first problem with the
switch statement is that it can be replaced by an
if statements without significant loss of clarity. In fact
switch is significantly less powerful than
if/else if/else because
if conditions can be more complex and
switch only compares values to constants. I am not saying that there are no cases where
switch looks better than
if but even then the value we gain is quite limited for the price we pay in the form of language complexity. Obviously if we have already paid the price there is no reason to not use it if it makes sense.
The Switch Statement Has Heavy Syntax
Just look at this syntax:
switch (day)
{
case WeekDay.Monday:
Console.WriteLine("Monday");
break;
case WeekDay.Tuesday:
Console.WriteLine("Tuesday");
break;
case WeekDay.Wednesday:
Console.WriteLine("Wednesday");
break;
case WeekDay.Thursday:
Console.WriteLine("Thursday");
break;
case WeekDay.Friday:
Console.WriteLine("Friday");
break;
case WeekDay.Saturday:
case WeekDay.Sunday:
Console.WriteLine("Weekend");
break;
default:
Console.WriteLine("Unknown day");
break;
}
It is littered with keywords (
switch,
case,
break,
default) and syntactic tokens like braces, columns and parenthesis. I know very few statements that are so heavy on syntax and all of them are language specific (for example Java anonymous classes). In addition the
switch statement has one very peculiar feature that is an exception from the rules of the C-style syntax – every case introduces a multi-line block without braces. Implicit blocks exist in C-style languages but they are always single-statement like the implicit blocks that can be used if your loop body is just a single statement.
It Is Easy to Make Mistakes with the Switch Statement
The most common mistakes in some popular C-style languages (C/C++, Java) is to forget the
break at the end of a
case statement. This is known as fall-through and is especially nasty since in most cases it will not even result in runtime error but in broken data.
The Switch Statement in C#
C# prevents this most common mistake by requiring a
break for every non-empty
case label. This leaves us with the obvious question what to do if the fall-through is the desired behavior. First of all it should be obvious that fall-through is rarely the desired behavior so it should not be the default. The actual problem is easily solved if we know that C# does not require specifically
break to be used with non-empty
case labels but requires some kind of unconditional jump. The unconditional jump is most commonly a
break statement but it can also be a
return,
throw,
goto or even a
continue statement for an enclosing loop. To solve the problem we simply use a goto like this:
switch (day)
{
case WeekDay.Monday:
case WeekDay.Tuesday:
case WeekDay.Wednesday:
case WeekDay.Thursday:
case WeekDay.Friday:
Console.WriteLine("Work day");
break;
case WeekDay.Saturday:
Console.WriteLine("First day of");
goto case WeekDay.Sunday;
case WeekDay.Sunday:
Console.WriteLine("Weekend");
break;
}
This solves one problem but there is another. If a
switch has no sensible default the
default case can be skipped. This is especially dangerous with enums because if a new enum value is added later it would result in silent error where no
case will be executed but potentially no error will occur. Ideally we want a compile-time error in this case like the one provided by F#'s match statement but obviously we cannot achieve this. What we can achieve is turn the silent error into a runtime error by adding a
default label that throws an exception. Looking at a
switch statement that handles the famous
Boolean enum.
switch(b)
{
case Bool.True:
//Do something
break;
case Bool.False:
case Bool.FileNotFound:
//Do something else
break;
default:
throw new Exception("Unexpected Boolean value");
//note we don’t need a break after a throw
}
This way if we add another Boolean value to the enum like AccessDenied we would at least get runtime error if we forget to change the
switch statement which is certainly better than the silent error that would happen if we did not have a
default.
Yes, I do realize this was incredibly long way to say "Always write a
default case to your
switch statements and throw an exception if you do not have suitable logic".