Don’t Be a Basic Coder And Use 5 Possibilities to Avoid The Bad Switch Case
Your ways to write truly flexible and scalable code
The first question that comes to your mind: why avoid switch-case constructs?
Imagine you are working on your dream project, and everything is working fine. You do your constant implementation of features. But then your mind strikes you with:
Why am I constantly adding new cases to any switch-case?
Why am I constantly changing code that worked fine before?
Fast forward a few months after you read this article, and you are truly able to build robust and scalable applications. Code written once, reused until the call tree burns. Adding new features without changing old code. Even an understandable git history guaranteed and a project/game/app evolved instead of mutating.
And this all because you’ve learned to avoid using switch-case.
The Reason Behind
You probably understood switch cases because they have a low learning curve and result in quite a comfortable usage. It is a default toolset of a beginner and intermediate programmer. But there are some drawbacks using them.
They work great during the creation of your initial set of cases, but once you have to extend them, they get unhandy and bloat your code.
Furthermore, when you want to map complex scenarios, the switch-case is a mess.
During my multiple years of C# development, I used switch cases a lot and ran into the disadvantages of switch-cases multiple times:
- No usage of variables means less flexibility and hard-coded cases
- No usage of the same constant in multiple cases means less flexibility during runtime
- No usage of relational expression (==, != ,<= etc.)
- No usage of float or constants
- Not expandable by redefining the environment, need to redefine switch-case
The Fundamental Problem of Switch-Case
Let’s imagine we want to make a trading card game, and we are about to realize a fundamental part of the game.
Drawing cards from the deck during a turn.
Therefore, we need the class of CardDeck we can later draw cards from.
The CardDeck is a class and has a field amountOfCards for the number of cards left to draw.
Doing It With Switch – Case
To step up from being a basic programmer, we have to think like a basic programmer first.
Hence the first approach aims for the usage of a switch-case.
The Draw() function will check that fact and accept the card deck and the type of cards as parameters:
Each drawing should tell us what kind of card we have drawn. There will be 4 options:
- Monster-Card
- Magic-Card
- Trap-Card
- Land-Card
If there is at least 1 card left, we get a random entry of cardTypes that goes into the switch-case. No matter what got drawn, calling PutCardToHand() will print out the type.
Now the Main function calls this code. We place this draw function inside a public class SwitchCaseUser meaning of the instantiation at first:
Also, the cardTypes are defined here as a string array. This code results into
While the cord works fine and does what it has been designed for, we are facing 3 crucial problems:
- The switch-case is hard-coded: Defining the types to draw outside will make this function tightly coupled with this outside world. Where does this function know what cards it is drawing? A change of outer space will have a change of the cases inside this function.
- Defining the card types that could be drawn inside this function leads to the same problem because now the function definitively knows what can be drawn. But why should a draw function have to know that? A draw function should draw, no matter what it is drawing. In this case, a change of the card types also means a change of the switch case.
- Expanding this behavior is clunky, and the code is not flexible. Because once we have a full functionally class that does exactly what it should do and do it well, it should stay how it is forever. We should program in the manner of code that works and should always work. Do not touch it again if outside behavior changes. That means passing all needed references to achieve this result and be independent of what has been passed.
The avoidance of a switch case will help to remove all of the mentioned drawbacks.
Avoiding Switch Case – 5 Possibilities
Again, we hand over the card deck and the card types into our Draw() function, but we don’t use the switch case this time. Instead, we are using five different methods to avoid the switch case.
Every head of the function and every first part of it will be the same for all five methods:
Inside the if-statement comes the interchangeable part:
The first and second options to avoid switch cases keep the original string array cardTypes. All other examples base on the conversion to a List<string>.
This is only a short comparison of meaningful substitutions of a switch case. If you want a deeper explanation of each technique, you can find them in the book Clean Code from Robert C. Martin.
1 For-Loop / ForEach-Loop
The for loop iterates the array and compares the cardType with all entries from the cardTypes and passes the match to the PutCardToHand function.
2 Local-Function
The local function does nearly the same as the for-loop, but it encapsulate it into a local function using a forEach loop.
Some malicious tongues might claim this is just a complicated way of doing it like #1, but this is a showcase, and local functions become interesting in a more advanced use case.
3 IndexOf-Method
Converting the array to a list now.
Since there is a list available to us, we can use some prior functions to aggregate those.
Our cardTypesList can now access a set of functions (full guide at the Microsoft docs). One is the indexOf method, and it returns the index of the item that matches our input.
Passing this one to our function PutCardToHand will result in the desired behavior.
4 Anonymous-Method
The anonymous method is like Wile E. Coyote and his obscure weapons to catch the roadrunner. Meep Meep.
We have to deal with defining overhead.
An anonymous function inside a named function as a parameter leads to double trouble function defining.
This is a pretty heavy way to code and, therefore, also not easy to comprehend afterward. Be aware!
((Func<string (inputParameter1), List<string> (inputParameter2), string (return parameter)>) anonymousFunction(inputParameter1, inputParameter2, returnParameter)
{
Lambda Expression returning cardType
}(passingParameter1, passingParameter2) <- Instant Invokation
A typical usage for this anonymous function would be when there is a large dataset passed into, and we need to aggregate just a subset of properties only at this specific part of the code, or another typical one would be when you only need to do something with data just here and right now once.
Therefore defining a named function and polluting your class space is not necessary.
Easier to comprehend way would be to store the anonymous function inside a delegate and then call it like so:
Nonetheless, anonymous functions should be held short.
5 Lambda-Expression
Anonymous functions are long and overkill in this scenario. I got you covered. Hence the lambda expression is easy and right for this kind of problem.
Look how short and fast you can get your result.
It is a simple oneliner with the usage of the Find() method for lists.
What is valid about anonymous functions is also valid here since Lambda Expressions are an enhanced anonymous function.
The Call in Main – Avoid Switch Case Finale
Since this is an avoidance of a switch case, I packed all alternatives into the SwitchCaseAvoider class.
Inside main, we call it just like the other one:
Will Result into
Conclusion
Not all of them are quite the best way to approach this specific situation.
Some of them (like the for loop or forEach loop) become calculation intense if you try to use them in large data sets to get one match. Also, there are built-in functionalities (E.g., for collections).
What they do have in common:
They are expandable by just redefining the environment, not the function itself.
Therefore, there is a significantly more important principle fulfilled than the best way to deal with a particular problem.
Your programs do not solve particular problems; they will always solve a composed problem or multiple ones. Fulfilling the overall principles like creating games with my SOLID Series in Unity is way more important.
Thank you for reading and if you want more outstanding processed programming content, visit my adventures on Udemy about C# and SQL.
Preselected Suiting Articles
How to Fix String Operations in C# After .NET 5 Update
Share This Article
Towards Data Science is a community publication. Submit your insights to reach our global audience and earn through the TDS Author Payment Program.
Write for TDS