C#
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C#
-
Section 4: Working with Data
-
Section 5: Error Handling
-
Section 6: Delegates and Events
-
Section 7: Async Programming
-
Section 8: More Language Features
-
Section 9: File I/O and Serialization
-
Section 10: Networking in .NET
-
Section 11: The .NET Ecosystem
-
Section 12: Memory and Performance
-
Section 13: Concurrency Beyond Async
-
Section 14: Reflection and Attributes
-
Section 15: Testing and Best Practices
-
Section 16: Design Patterns in C#
-
Section 17: Standard Library Deep Dive
-
Section 18: Data Structures and Algorithms in C#
-
Section 19: GUI and Desktop Development Overview
-
Section 20: Practical Projects
-
Section 21: More Practice Exercises
-
Section 22: More Standard Library and Text Processing
-
Section 23: More Design Patterns
-
Section 24: More Projects
-
Section 25: Interview Practice
-
Section 26: C# Keywords Reference (Modifiers)
-
Section 27: C# Keywords Reference (Statements)
-
Section 28: C# Keywords Reference (Operators)
-
Section 29: BCL: System.Collections.Generic
-
Section 30: BCL: System.Linq
-
Section 31: BCL: System.Threading
-
Section 32: BCL: System.IO
-
Section 33: BCL: System.Text and System.Text.Json
-
Section 34: BCL: System.Net.Http
-
Section 35: C# Language Specification Topics
-
Section 36: More Practice Exercises
-
Section 37: More Async Patterns
-
Section 38: More BCL: System.Reflection and System.Diagnostics
-
Section 39: Nullable Reference Types In Depth
-
Section 40: C# Records and Pattern Matching In Depth
-
Section 41: Dependency Injection Deep Dive
-
Section 42: More Interview and Whiteboard Practice
11: Control Flow: Switch Statements and Switch Expressions
A few years ago, I was reviewing a pull request for a junior developer who had built a notification dispatcher. He had this monstrous if-else if chain checking for NotificationType. Every time the team added a new channel—Slack, SMS, Email, PagerDuty—the block grew. By the time he reached the fifth condition, the code was indented so far to the right that he was practically writing in the gutter of the IDE. It was an indentation nightmare and, more importantly, a maintenance landmine. I remember telling him, "You're treating C# like it's 1995. Let's clean this up."
Taming the Logic with Switch Statements
When you're comparing a single variable against a long list of possible constant values, a switch statement is your best friend. It's cleaner than a chain of if statements and tells anyone reading your code exactly what the intent is: "I have one value, and I need to do something different depending on what that value is."
Here is how we would have refactored that notification dispatcher. Instead of checking the type over and over, we jump straight to the matching case:
string notificationType = "Email";
switch (notificationType)
{
case "Email":
Console.WriteLine("Sending SMTP request...");
break;
case "SMS":
Console.WriteLine("Connecting to Twilio API...");
break;
case "Slack":
Console.WriteLine("Posting to Webhook...");
break;
default:
Console.WriteLine("Unknown notification type. Logging error.");
break;
}
One thing to keep in mind: C# requires that break (or return/throw) at the end of every case. You can't "fall through" from one case to another like you can in some older languages. If you want two cases to execute the same code, you just stack them on top of each other without a break in between.
The Power of Switch Expressions
Now, if you're using a modern version of C#, you'll find that we rarely use the classic switch statement when we just want to assign a value to a variable. That's where switch expressions come in. Introduced in C# 8.0, these are essentially a "compressed" version of the switch statement that returns a value directly.
I personally prefer these because they remove all the boilerplate—no case keywords, no break statements, and no curly brace clutter. Imagine you're writing a system to determine the access level of a user based on their role:
string userRole = "Editor";
int accessLevel = userRole switch
{
"Admin" => 10,
"Editor" => 5,
"Viewer" => 1,
_ => 0 // The underscore is the 'discard' pattern, acting as the default
};
Notice the _ symbol. In a switch expression, that's your default case. It's mandatory if the compiler can't prove that you've covered every single possible value of the input type. If you forget it, C# will throw a warning (or an exception at runtime) because it doesn't want you accidentally leaving a value unhandled.
Pattern Matching and Logic Guards
The real magic happens when you stop switching on simple strings or ints and start using patterns. You can actually put logic inside your switch arms using the when keyword. This allows you to handle complex conditions while keeping the structural beauty of the switch.
Let's say you're calculating shipping costs based on a package's weight and its destination. You can mix constant matches with logical guards:
decimal CalculateShipping(double weight, string zone) => weight switch
{
< 1.0 => 5.00m,
< 5.0 when zone == "Domestic" => 10.00m,
< 5.0 when zone == "International" => 25.00m,
_ => 50.00m
};
I love this approach because it reads almost like a business requirement document. "If it's under 1kg, it's $5. If it's under 5kg and domestic, it's $10." It's readable, concise, and far less prone to the "off-by-one" errors I've seen in nested if blocks.
📋 Practical Task
Exercise: Elemental Damage Multiplier
You are building a combat system for an RPG. You need to create a method that calculates a damage multiplier based on the attackerElement and the defenderElement.
Requirements:
- Use a switch expression to determine the multiplier.
- If the attacker is
"Fire"and the defender is"Grass", the multiplier should be2.0(Super Effective). - If the attacker is
"Fire"and the defender is"Water", the multiplier should be0.5(Not Very Effective). - If the attacker is
"Water"and the defender is"Fire", the multiplier should be2.0. - If the attacker is
"Water"and the defender is"Grass", the multiplier should be0.5. - For any other combination, the multiplier should be
1.0(Neutral).
Hint: Since you are checking two different variables, you can switch on a Tuple. For example: (attackerElement, defenderElement) switch { ... }
There are no comments for now.