Code4IT

The place for .NET enthusiasts, Azure lovers, and backend developers

C# Tip: Advanced Switch Expressions and Switch Statements using filters

2023-05-16 5 min read CSharp Tips

We all use switch statements in our code. Do you use them at their full potential?

Table of Contents

Just a second! 🫷
If you are here, it means that you are a software developer. So, you know that storage, networking, and domain management have a cost .

If you want to support this blog, please ensure that you have disabled the adblocker for this site. I configured Google AdSense to show as few ADS as possible - I don't want to bother you with lots of ads, but I still need to add some to pay for the resources for my site.

Thank you for your understanding.
- Davide

We all use switch statements in our code: they are a helpful way to run different code paths based on an check on a variable.

In this short article, we’re gonna learn different ways to write switch blocks, and some nice tricks to create clean and easy-to-read filters on such statements.

For the sake of this example, we will use a dummy hierarchy of types: a base User record with three subtypes: Player, Gamer, and Dancer.

public abstract record User(int Age);

public record Player(int Age, string Sport) : User(Age);

public record Gamer(int Age, string Console) : User(Age);

public record Dancer(int Age, string Dance) : User(Age);

Let’s see different usages of switch statements and switch expressions.

Switch statements

Switch statements are those with the standard switch (something) block. They allow for different executions of paths, acting as a list of if - else if blocks.

They can be used to return a value, but it’s not mandatory: you can simply use switch statements to execute code that does not return any value.

Switch statements with checks on the type

The most simple example we can have is the plain check on the type.

User user = new Gamer(30, "Nintendo Switch");

string message = "";

switch (user)
{
    case Gamer:
    {
        message = "I'm a gamer";
        break;
    }
    case Player:
    {
        message = "I'm a player";
        break;
    }
    default:
    {
        message = "My type is not handled!";
        break;
    }
}

Console.WriteLine(message); // I'm a player

Here we execute a different path based on the value the user variable has at runtime.

We can also have an automatic casting to the actual type, and then use the runtime data within the case block:

User user = new Gamer(30, "Nintendo Switch");

string message = "";

switch (user)
{
    case Gamer g:
    {
        message = "I'm a gamer, and I have a " + g.Console;
        break;
    }
    case Player:
    {
        message = "I'm a player";
        break;
    }
    default:
    {
        message = "My type is not handled!";
        break;
    }
}

Console.WriteLine(message); //I'm a gamer, and I have a Nintendo Switch

As you can see, since user is a Gamer, within the related branch we cast the user to Gamer in a variable named g, so that we can use its public properties and methods.

Filtering using the WHEN keyword

We can add additional filters on the actual value of the variable by using the when clause:

User user = new Gamer(3, "Nintendo");

string message = "";

switch (user)
{
    case Gamer g when g.Age < 10:
    {
        message = "I'm a gamer, but too young";
        break;
    }
    case Gamer g:
    {
        message = "I'm a gamer, and I have a " + g.Console;
        break;
    }
    case Player:
    {
        message = "I'm a player";
        break;
    }
    default:
    {
        message = "My type is not handled!";
        break;
    }
}

Console.WriteLine(message); // I'm a gamer, but too young

Here we have the when g.Age < 10 filter applied to the Gamer g variable.

Clearly, if we set the age to 30, we will see I’m a gamer, and I have a Nintendo Switch.

Switch Expression

Switch expressions act like Switch Statements, but they return a value that can be assigned to a variable or, in general, used immediately.

They look like a lightweight, inline version of Switch Statements, and have a slightly different syntax.

To reach the same result we saw before, we can write:

User user = new Gamer(30, "Nintendo Switch");

string message = user switch
{
    Gamer g => "I'm a gamer, and I have a " + g.Console,
    Player => "I'm a player",
    _ => "My type is not handled!"
};

Console.WriteLine(message);

By looking at the syntax, we can notice a few things:

  • instead of having switch(variable_name){}, we now have variable_name switch {};
  • we use the arrow notation => to define the cases;
  • we don’t have the default keyword, but we use the discard value _.

When keyword vs Property Pattern in Switch Expressions

Similarly, we can use the when keyword to define better filters on the cases.

string message = user switch
{
    Gamer gg when gg.Age < 10 => "I'm a gamer, but too young",
    Gamer g => "I'm a gamer, and I have a " + g.Console,
    Player => "I'm a player",
    _ => "My type is not handled!"
};

You can finally use a slightly different syntax to achieve the same result. Instead of using when gg.Age < 10 you can write Gamer { Age: < 10 }. This is called Property Pattern

string message = user switch
{
    Gamer { Age: < 10 } => "I'm a gamer, but too young",
    Gamer g => "I'm a gamer, and I have a " + g.Console,
    Player => "I'm a player",
    _ => "My type is not handled!"
};

Further readings

We actually just scratched the surface of all the functionalities provided by the C# language.

First of all, you can learn more about how to use Relational Patterns in a switch expression.

To have a taste of it, here’s a short example:

string Classify(double measurement) => measurement switch
{
    < -4.0 => "Too low",
    > 10.0 => "Too high",
    double.NaN => "Unknown",
    _ => "Acceptable",
};

but you can read more here:

πŸ”— Relational patterns | Microsoft Docs

This article first appeared on Code4IT 🐧

There are also more ways to handle Switch Statements. To learn about more complex examples, here’s the documentation:

πŸ”— The switch statement | Microsoft Docs

Finally, in those examples, we used records. As you saw, I marked the User type as abstract.

Do you want to learn more about Records?

πŸ”— 8 things about Records in C# you probably didn’t know | Code4IT

Wrapping up

Learning about tools and approaches is useful, but you should also stay up-to-date with language features.

Switch blocks had a great evolution over time, making our code more concise and distraction-free.

I hope you enjoyed this article! Let’s keep in touch on Twitter or LinkedIn! πŸ€œπŸ€›

Happy coding!

🐧

About the author

Davide Bellone is a Principal Backend Developer with more than 10 years of professional experience with Microsoft platforms and frameworks.

He loves learning new things and sharing these learnings with others: that’s why he writes on this blog and is involved as speaker at tech conferences.

He's a Microsoft MVP πŸ†, conference speaker (here's his Sessionize Profile) and content creator on LinkedIn.