Cleaner Code With Switch Expressions In C#
[C#, Algorithms]
Everyone who remembers their path towards becoming a programmer, either as a hobbyist or a career software engineer, will remember the problem of writing this program - given a mark, allocate a grade according to a table like this:
Marks | Grade |
---|---|
< 40 | F |
40 - 49 | E |
50 - 59 | D |
60 - 69 | C |
70 - 79 | B |
80 - 100 | A |
Other | X |
Your first attempt will probably look like this:
char GradeByIf(int grade)
{
if (grade < 40)
return 'F';
else if (grade >= 40 && grade < 50)
return 'E';
else if (grade >= 50 && grade < 60)
return 'D';
else if (grade >= 60 && grade < 70)
return 'C';
else if (grade >= 70 && grade < 80)
return 'B';
else if (grade >= 80 && grade <= 100)
return 'A';
return 'X';
}
If you look at this closer you realize there is some repetition going on here:
So the code can be simplified even further, if we remove the redundancy AND change the order of the conditions.
char GradeByIfV2(int grade)
{
if (grade > 100)
return 'X';
else if (grade >= 80)
return 'A';
else if (grade >= 70)
return 'B';
else if (grade >= 60)
return 'C';
else if (grade >= 50)
return 'D';
else if (grade >= 40)
return 'E';
else
return 'F';
}
You can also use switch
statements to achieve this goal. Like so:
char GradeBySwitchV1(int grade)
{
switch (grade)
{
case int x when x < 40:
return 'F';
case int x when x >= 40 && x < 50:
return 'E';
case int x when x >= 50 && x < 60:
return 'D';
case int x when x >= 60 && x < 70:
return 'C';
case int x when x >= 70 && x < 80:
return 'B';
case int x when x >= 80 && x <= 100:
return 'A';
default:
return 'X';
}
}
This can be simplified even further with a slight modification:
char GradeBySwitchV2(int grade)
{
switch (grade)
{
case int x when x < 40:
return 'F';
case int x when x is >= 40 and < 50:
return 'E';
case int x when x is >= 50 and < 60:
return 'D';
case int x when x is >= 60 and < 70:
return 'C';
case int x when x is >= 70 and < 80:
return 'B';
case int x when x is >= 80 and <= 100:
return 'A';
default:
return 'X';
}
}
Now these look almost identical:
The is
and and
are not just syntactic sugar - they can also help the compiler find errors in your code.
So if in the IDE I introduce the following bug:
case int x when x is >= 40 and < 40:
return 'E';
Note how the compiler notifies you after it attempts to evaluate your conditions:
The compiler does not attempt to validate the previous version of the function.
C# 8 introduced switch expressions to simplify code like this still further:
char GradeBySwitchV3(int grade) =>
grade switch
{
int x when x < 40 => 'F',
int x when x is >= 40 and < 50 => 'E',
int x when x is >= 50 and < 60 => 'D',
int x when x is >= 60 and < 70 => 'C',
int x when x is >= 70 and < 80 => 'B',
int x when x is >= 80 and <= 100 => 'A',
_ => 'X'
};
Of interest is this part - _ => 'X'
. Here _ =>
is the equivalent of default
in a traditional switch statement - it matches anything else.
As from the if example above, this can be simplified further:
char GradeBySwitchV4(int grade) =>
grade switch
{
int x when x > 100 => 'X',
int x when x is >= 80 => 'A',
int x when x is >= 70 => 'B',
int x when x is >= 60 => 'C',
int x when x is >= 50 => 'D',
int x when x is >= 40 => 'E',
_ => 'F'
};
It is still possible to simplify this code further, believe it or not! This is done by removing the variable declarations. This feature is available from C# 9 going forwards.
char GradeBySwitchV5(int grade) =>
grade switch
{
< 40 => 'F',
>= 40 and < 50 => 'E',
>= 50 and < 60 => 'D',
>= 60 and < 70 => 'C',
>= 70 and < 80 => 'B',
>= 80 and <= 100 => 'A',
_ => 'X'
};
And finally we can still simplify this to remove the redundancy to get our final version:
char GradeBySwitchV6(int grade) =>
grade switch
{
> 100 => 'X',
>= 80 => 'A',
>= 70 => 'B',
>= 60 => 'C',
>= 50 => 'D',
>= 40 => 'E',
_ => 'F'
};
If you are wondering at this terse, almost cryptic syntax - this is borrowed heavily from functional programming paradigm.
The equivalent program in F# would look like this:
let grade(x) = match x with
| x when x > 100 -> 'X'
| x when x >= 80 -> 'A'
| x when x >= 70 ->'B'
| x when x >= 60 ->'C'
| x when x >= 50 ->'D'
| x when x >= 40 ->'E'
| x when x < 40 ->'F'
| _-> 'X'
The code is in my Github.
Happy hacking!