C# 9.0: Pattern Matching in Switch Expressions
In the previous blog posts about C# 9.0 you learned about different features:
- Top-level statements
- Init-only properties
- Records
- Target-typed new expressions
- Improved Pattern Matching
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.
Comments (19)
Awesome explanation.
Thank you Anil, great to hear that you like it.
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.
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
thx, very nice.
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.
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. :)
Clear explanation. Great. Thank you!
You’re welcome Tilahun
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
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..
Really grateful for such clear and precise explanation. It’s a pleasure to read this article!
Thank you David
Thanks Thomas.
What I like most: 1984 => “Read George Orwell’s book” :)
You are awesome! Hope you’re doing well!
Nice post. https://stackoverflow.com/questions/59687295/can-i-use-pattern-matching-for-code-like-this-on-generic-types
Indeed. :)
I think it’s one of the simplest explanation I’ve read. Thank you.
Thank you Edward
“Stare into the void”. I lost it lolllll. Good post.