C# 9.0: Pattern Matching in Switch Expressions

In the previous blog posts about C# 9.0 you learned about different features:

In this blog post, let’s look at C# 9.0 pattern matching in switch expressions.

In the previous blog post you learned about using patterns with the is pattern expression, and you learned about the new relational patterns in C# 9.0, and also about the new pattern combinators and and or. If you haven’t read the previous blog post, I recommend you to read it, as I don’t repeat the basics of relational patterns and pattern combinators in this blog post.

The is pattern expression allows you to evaluate patterns. But you can also evaluate patterns in a switch expression. In this blog post, we use switch expressions. But before we do that, let’s go a bit back and let’s look at switch statements before C# 7.0 to understand how they have evolved.

Switch Statements Before C# 7.0

Since C# 1.0, you can write switch statements in your code. You usually do this instead of writing if/else if/else logic like you see it in the code snippet below.

var developer = new Developer { FirstName = "Julia" };

string favoriteTask;

if (developer.FirstName == "Julia")
{
  favoriteTask = "Writing code";
}
else if (developer.FirstName == "Thomas")
{
  favoriteTask = "Writing this blog post";
}
else
{
  favoriteTask = "Watching TV";
}

Instead of the if/else stuff above, you can create a beautiful switch statement like below:

string favoriteTask;

switch (developer.FirstName)
{
  case "Julia":
    favoriteTask = "Writing code";
    break;
  case "Thomas":
    favoriteTask = "Writing this blog post";
    break;
  default:
    favoriteTask = "Watching TV";
    break;
}

But switch statements were a bit limited before C# 7.0. Let’s say you have the class structure like below:

public class Person
{
  public string FirstName { get; set; }
  public int YearOfBirth { get; set; }
}
public class Developer : Person
{
  public Manager Manager { get; set; }
}
public class Manager : Person
{
}

Now let’s say that you want to switch an object variable by the type that was assigned to that variable. When you try to use an object variable as input for the switch statement, you get the error that you see below if you use C# 6.0 or earlier:

So, you see, before C# 7.0 you can only use bool, char, string, integral types like int and float, enumerations, and corresponding nullable types. But you can’t switch for example by an object variable like I try it in the screenshot above.

Another problem is that a case in the switch statement requires a constant value, and that constant value has the same type limitations. For example, the following switch statement does not work in C# 6.0 or earlier because of two reasons: Firstly, I try to switch by an object, and secondly, the typeof keyword resolves a type, it is not a constant value. This means that the code that you see in the snippet below does not compile. (Note: Using typeof for the case label does not compile in any C# version):

string favoriteTask;
switch (obj) // Switching by object is not supported before C# 7.0
{
  case typeof(Developer): // typeof does not work here in any C# version
    favoriteTask = "Write code";
    break;
  case typeof(Manager):
    favoriteTask = "Create meetings";
    break;
  default:
    favoriteTask = "Listen to music";
    break;
}

Before C# 7.0, you had to solve the problem above by switching by type name, which means that you switch by string, which is supported. In the code below I do this, and for the cases I use the string constants “Developer” and “Manager”. This works fine.

switch (obj.GetType().Name)
{
  case "Developer": // That's actually the so-called constant pattern
    favoriteTask = "Write code";
    break;
  case "Manager":
    favoriteTask = "Create meetings";
    break;
  default:
    favoriteTask = "Listen to music";
    break;
}

With the nameof keyword that was introduced with C# 6.0, you can even let the compiler generate the strings “Developer” and “Manager” from the class names like you see it below. This helps you to avoid typos in your code, as the compiler will generate the strings from the class names for you:

switch (obj.GetType().Name)
{
  case nameof(Developer):
    favoriteTask = "Write code";
    break;
  case nameof(Manager):
    favoriteTask = "Create meetings";
    break;
  default:
    favoriteTask = "Listen to music";
    break;
}

But now, what if you want to use the developer’s firstname? Then you have to cast the obj variable to a Developer like you see it in the statement below.

switch (obj.GetType().Name)
{
  case nameof(Developer):
    var dev = (Developer)obj;
    favoriteTask = $"{dev.FirstName} writes code";
    break;
  case nameof(Manager):
    favoriteTask = "Create meetings";
    break;
  default:
    favoriteTask = "Listen to music";
    break;
}

Patterns in Switch Statements with C# 7.0

C# 7.0 introduced the support for type patterns in switch statements. You can switch by any type, and you can use patterns in your switch statement. Look at the switch statement in the code snippet below. I switch by an object variable. Then I use the type pattern to check if the object is a Developer or a Manager. When you don’t need for example the Developer object to be stored in a variable, you use a discard, which is an underscore.

object obj = new Developer { FirstName = "Thomas", YearOfBirth = 1980 };

string favoriteTask;

switch (obj) // Since C# 7.0, any type is supported here
{
  case Developer _: // Type pattern with discard (_)
    favoriteTask = "Write code";
    break;
  case Manager _:
    favoriteTask = "Create meetings";
    break;
  case null: // The null pattern
    favoriteTask = "Look into the void";
    break;
  default:
    favoriteTask = "Listen to music";
    break;
}

Instead of using a discard, you can define a variable that you use in the case block:

case Developer dev: // Type pattern with a variable name
  favoriteTask = $"{dev.FirstName} writes code";
  break;

C# 7.0 also introduced when conditions for the cases. They work pretty much like an if statement, just inside of a switch statement on a specific case. Do you want to define a case that checks if the type is a Developer, and if they were born in the eighties? It’s like this in C# 7.0 with when conditions:

case Developer dev when dev.YearOfBirth >= 1980 && dev.YearOfBirth <= 1989:
  favoriteTask = $"{dev.FirstName} listens to heavy metal while coding";
  break;

Note that the when condition is like an if condition, so don’t mix this up with relational patterns and pattern combinators that were introduced with C# 9.0. The when condition above uses like an if condition the relational operators (operators!!!, not patterns) >= and <= and the boolean logical AND operator && to combine the two bool values.

Important is now also the order of the cases. Look at the switch statement below. If you pass in a Developer object, three different cases match, and only the first one that matches is used:

object obj = new Developer { FirstName = "Thomas", YearOfBirth = 1980 };

string favoriteTask;

switch (obj)
{
  case Developer dev when dev.YearOfBirth >= 1980 && dev.YearOfBirth <= 1989:
    // 1. This case is taken for the defined Developer object
    favoriteTask = $"{dev.FirstName} listens to heavy metal while coding";
    break;
  case Developer dev: 
    // 2. This case matches too, but it's defined after the first one that matches
    favoriteTask = $"{dev.FirstName} writes code";
    break;
  case Person _: 
    // 3. This case matches too for a Developer, as Person is a base class
    favoriteTask = "Eat and sleep";
    break;
  default:
    favoriteTask = "Do what objects do";
    break;
}

That’s what was added with C# 7.0. Now, let’s move on to C# 8.0.

Switch Expressions and Pattern Matching in C# 8.0

C# 8.0 introduced switch expressions. Let’s take the simple switch statement that I wrote at the beginning of this blog post:

string favoriteTask;

switch (developer.FirstName)
{
  case "Julia":
    favoriteTask = "Writing code";
    break;
  case "Thomas":
    favoriteTask = "Writing this blog post";
    break;
  default:
    favoriteTask = "Watching TV";
    break;
}

When you use C# 8.0 or later, you can put the cursor in Visual Studio on that switch statement, and Visual Studio will suggest you to convert it to a switch expression:

The code that you get is the beautiful switch expression that you see in the following code snippet. Note how all the case and break clutter went away, and how readable it is. A case in a switch expression is actually a so-called switch expression arm:

string favoriteTask = developer.FirstName switch
{
  "Julia" => "Writing code", // This is the first switch expression arm
  "Thomas" => "Writing this blog post",
  _ => "Watching TV",
};

The code above will blow up if the developer variable is null, as the FirstName property is accessed with the dot syntax. You can also pass the full Developer object to the switch expression to use property patterns like in the code snippet below. Property patterns were also introduced with C# 8.0:

string favoriteTask = developer switch
{
  { FirstName: "Julia" } => "Writing code",
  { FirstName: "Thomas" } => "Writing this blog post",
  _ => "Watching TV",
};

Now let’s look at another C# 7.0 switch statement, where I switch by type:

string favoriteTask;

switch (obj)
{
  case Developer dev when dev.YearOfBirth == 1980:
    favoriteTask = $"{dev.FirstName} listens to metal";
    break;
  case Developer dev:
    favoriteTask = $"{dev.FirstName} writes code";
    break;
  case Manager _:
    favoriteTask = "Create meetings";
    break;
  default:
    favoriteTask = "Do what objects do";
    break;
}

Also the switch statement above can be converted to a switch expression that you see in the code snippet below:

string favoriteTask = obj switch
{
  Developer dev when dev.YearOfBirth == 1980 => $"{dev.FirstName} listens to metal",
  Developer dev => $"{dev.FirstName} writes code",
  Manager _ => "Create meetings",
  _ => "Do what objects do",
};

Instead of the when condition to check the YearOfBirth property, you can use a property pattern:

string favoriteTask = obj switch
{
  Developer { YearOfBirth: 1980 } dev => $"{dev.FirstName} listens to metal",
  Developer dev => $"{dev.FirstName} writes code",
  Manager _ => "Create meetings",
  _ => "Do what objects do",
};

But now, what if you want to check if the developer was born in the eighties? You can’t do that in C# 8.0 with a property pattern, as relational patterns and pattern combinators are not supported, they were introduced with C# 9.0. This means, to check in C# 8.0 if a developer was born in the eighties, you have to use a when condition like in this code snippet:

string favoriteTask = obj switch
{
  Developer dev when dev.YearOfBirth >= 1980 && dev.YearOfBirth <= 1989
    => $"{dev.FirstName} listens to metal",
  Developer dev => $"{dev.FirstName} writes code",
  Manager _ => "Create meetings",
  _ => "Do what objects do",
};

Relational Patterns and Pattern Combinators in C# 9.0

You learned already in the previous blog post about the relational patterns >, >=, <, and <=, and also about the pattern combinators and and or. Relational patterns and pattern combinators were introduced with C# 9.0, and you can use them not only with is expressions, but also in switch expressions. The following code snippet uses a C# 8.0 style when condition with relational operators to check if the developer was born in the eighties:

string favoriteTask = obj switch
{
  Developer dev when dev.YearOfBirth >= 1980 && dev.YearOfBirth <= 1989 
    => $"{dev.FirstName} listens to metal",
  _ => "Dance like no one is watching"
};

With C# 9.0, you can write the when condition also with the is expression and with relational patterns and the and pattern combinator like in the following snippet. Visual Studio actually suggests this to you. Note how you write the YearOfBirth property in the code snippet below just once, and then you use the is expression with combined relational patterns to check the shape of the property. In the code snippet above, we wrote the YearOfBirth property twice, as we didn’t use combined relational patterns, but instead combined bool values and relational operators.

string favoriteTask = obj switch
{
  Developer dev when dev.YearOfBirth is >= 1980 and <= 1989
    => $"{dev.FirstName} listens to metal",
  _ => "Dance like no one is watching"
};

But instead of using the when condition, you can also use a property pattern, which looks even nicer and more compact:

string favoriteTask = obj switch
{
  Developer { YearOfBirth: >= 1980 and <= 1989 } dev
    => $"{dev.FirstName} listens to metal",
  _ => "Dance like no one is watching"
};

As you can see, you can use all the C# 9.0 pattern matching magic in your switch expression. Do you want to create a switch expression arm for developers born in the eighties, but not in 1984? Here we go:

string favoriteTask = obj switch
{
  Developer { YearOfBirth: >= 1980 and <= 1989 and not 1984 } dev
    => $"{dev.FirstName} listens to heavy metal while coding",
  Developer dev => $"{dev.FirstName} writes code",
  Manager _ => "Create meetings",
  _ => "Do what objects do",
};

Also an addition is that you don’t have to define a discard if you don’t need the variable. So, instead of

Manager _ => "Create meetings",

you can write with C# 9.0 just

Manager => "Create meetings",

You would still use a discard to define the default case in your switch expression:

_ => "Do what objects do",

If you have already a variable of type Developer, you don’t have to use the type pattern. You can use property patterns directly, you can also use for example the negotiated null pattern like you see it here:

string favoriteTask = dev switch
{
  { YearOfBirth: >= 1980 and <= 1989 and not 1984 } 
    => $"{dev.FirstName} listens to heavy metal while coding",
  not null => $"{dev.FirstName} writes code",
  _ => "Look into the void",
};

Like with is expressions, you can also nest property patterns. To create a switch expression arm for developers whose manager is born in 1980, you can write it like this:

string favoriteTask = dev switch
{
  { Manager: { YearOfBirth: 1980 } } => "Manager listens to heavy metal",
  not null => $"{dev.FirstName} writes code",
  _ => "Look into the void",
};

If you have a simple type like an int variable like you see it in the code snippet below, you can also write the patterns directly without any curlies:

int yearOfBirth = 1980;
string favoriteTask = yearOfBirth switch
{
  1984 => "Read George Orwell's book", // Constant pattern
  >= 1980 and <= 1989 => "Listen to heavy metal", // Combined relational patterns
  > 1989 => "Write emails like everyone is watching", // Relational pattern
  _ => "Dance like no one is watching", // <- This comma here is optional
};

Oh, one more thing: Did you notice the last comma behind the last switch expression arm in the code snippet above? I think so, as I added a comment there, right?! That is the so-called trailing comma, and it’s optional. Sometimes when you copy and paste switch expression arms to change the order, it’s a bit simpler if you can keep that trailing comma.

Summary

As you saw in this blog post, C# 7.0 introduced patterns in switch statements, C# 8.0 introduced switch expressions and more patterns like property patterns, and C# 9.0 introduced the relational patterns and pattern combinators that you can use in your switch expressions.

I hope you enjoyed reading this blog post.

Finally, I want to say that not all people born in the eighties listen to heavy metal. But I do. :-)

In the next blog post, you will learn about covariant return types in C# 9.0.

Share this post

Comments (19)

  • Anil Kumar Reply

    Awesome explanation.

    May 14, 2021 at 12:48 am
  • Atilla Selem Reply

    I’d like to thank you for all courses on Pluralsight. They are awesome.
    You are teaching to C# developers with an amazing refactoring and abstraction mind-set.
    I ve learned so much about MVVM, XAML, TDD etc..
    Definitely a 5-star MVP.

    Regards,

    Atilla.

    June 29, 2021 at 8:27 am
    • Thomas Claudius Huber Reply

      Hey Atilla, thank you so much for your feedback. I’m happy to read this, and your comment was worth creating all those courses! Thank you, Thomas

      July 7, 2021 at 9:15 am
  • Tzwenni Reply

    thx, very nice.

    July 11, 2021 at 9:02 pm
  • nate Reply

    I was born in 1988, and I’m listening to metal while coding (while looking through this). That’s good to know about the property patterns.

    October 15, 2021 at 4:59 pm
    • Thomas Claudius Huber Reply

      That’s great to hear Nathan. I actually love metal from the 80s. :) Iron Maiden, Whitesnake, Skid Row, Manowar, W.A.S.P, Judas Priest, Metallica etc. :)

      October 18, 2021 at 9:09 am
  • Tilahun Reply

    Clear explanation. Great. Thank you!

    February 25, 2022 at 2:13 am
  • Yaniv Reply

    What about multiple variables, functions calls and etc under same case?

    case 1 :
    functionA;
    functionB;
    break;
    case 2:
    functionC;
    break;

    ?

    is it like that:

    1=> functionA, FunctionB,
    2=> functionC,
    _ default…. ?

    thanks

    March 14, 2022 at 4:17 pm
  • yaniv Reply

    Hi

    I have a question about this pattern:

    if I have multiple lines under same case, how can I write this in new C# switch case?

    for example

    swtich (x){
    case 1:
    something = list1();
    something2 = list2();
    break;
    case 2:
    something3 = lsit3();
    break;
    default:
    break;

    ?

    thank you very much..

    March 14, 2022 at 4:54 pm
  • David Reply

    Really grateful for such clear and precise explanation. It’s a pleasure to read this article!

    March 22, 2022 at 1:26 pm
  • Christof Reply

    Thanks Thomas.
    What I like most: 1984 => “Read George Orwell’s book” :)
    You are awesome! Hope you’re doing well!

    September 9, 2022 at 9:12 am
  • Edward Reply

    I think it’s one of the simplest explanation I’ve read. Thank you.

    November 25, 2022 at 12:13 am
  • Teddy Smith Reply

    “Stare into the void”. I lost it lolllll. Good post.

    March 5, 2024 at 6:25 pm

Leave a Reply to Atilla Selem Cancel reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.