C# 9.0: Improved Pattern Matching
In the previous blog posts about C# 9.0 you learned about different features:
In this blog post, let’s look at another feature of C# 9.0, the improved pattern matching.
What is Pattern Matching?
Before we clarify what pattern matching is, let’s first understand what a pattern is. The Microsoft Docs say this:
Patterns test that a value has a certain shape, and can extract information from the value when it has the matching shape.
https://docs.microsoft.com/dotnet/csharp/pattern-matching
Ok, so a pattern checks if a value has a certain shape, and if that’s the case, the pattern matches – that’s why it’s called pattern matching. A pattern can also extract information that you can use in your code. You usually use patterns in your code already today. Let’s look at the is
pattern expression.
The is Pattern Expression
The if
statement in the code snippet below checks with the is
pattern expression if the obj
variable contains a Developer
instance. The is
keyword is a pattern expression, which means it can evaluate a specific pattern. In the code snippet below the Type Pattern is used with the is
pattern expression. The type Developer
is used to check if the obj
variable contains an instance of type Developer
. In the body of the if
statement, the code extracts the information into a developer
variable:
if (obj is Developer)
{
var developer = (Developer)obj;
// Do something with the developer
}
Often you see the code above also written like below with the as
operator that returns null
if the casting is not valid.
var developer = obj as Developer
if (developer != null)
{
// Do something with the developer
}
Since C# 7.0, the is
pattern expression allows you to use a more appropriate syntax to do the same thing as in the code snippets above. In the condition of the if
block you can check the type with the is
keyword like below. If the type matches, you can store the Developer
object directly in a developer
variable that you declare after the is
expression like below:
if (obj is Developer developer)
{
// Do something with the developer
}
Property Patterns in C# 8.0
Pattern matching was improved with C# 8.0, and Property Patterns
were added that let you do stuff like below. Only if the obj
variable is of type Developer
and only if its FirstName
property contains the string Thomas, the if
block is entered:
if (obj is Developer { FirstName: "Thomas" } developerWithFirstNameThomas)
{
// Use the developerWithFirstNameThomas variable here
}
Property Patterns are even way more powerful, because you can nest them. Let’s say you have the class structure below. As you can see, Developer
and Manager
both inherit from the Person
class, which has a FirstName
property and a YearOfBirth
property. The Developer
class has a Manager
property of type Manager
.
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 you want to check if a variable contains a Developer
instance that has a Manager
with the firstname Thomas. You can do this by nesting property patterns like you see it in the code snippet below:
if (obj is Developer { Manager: { FirstName: "Thomas" } } developerWithThomasAsManager)
{
// Use the developerWithThomasAsManager variable here
}
What if you want to check if the Manager
‘s firstname has a length of 6? You can nest the property patterns even more to access the Length
property of the type string
, which is the type of the FirstName
property:
if (obj is Developer { Manager: { FirstName: { Length: 6 } } } developerWithManagerFirstnameLengthOfSix)
{
// Use the developerWithManagerFirstnameLengthOfSix variable here
}
You can also check multiple properties by separating them with a comma like in the next code snippet. There the developer’s YearOfBirth
property and also its Manager
‘s FirstName
property are checked:
if (obj is Developer { YearOfBirth: 1980, Manager: { FirstName: "Thomas" } } developer)
{
// Use the developer variable here
}
Now, let’s go back to a simple property pattern. Let’s check if the obj
variable contains a Developer
and that the dev was born in 1980. You can do that with the condition that you see below:
if (obj is Developer { YearOfBirth: 1980 } devBornIn1980)
{
// Use the devBornIn1980 variable here
}
All the code that you’ve seen so far works with C# 8.0.
But now, what if you want to check if the YearOfBirth
property is greater or equal to 1980? You can do this in C# 9.0 with Relational Patterns.
C# 9.0: Relational Patterns
C# 9.0 introduces relational patterns. You can use them to check if the input is less than (<
), greater than (>
), less than or equal (<=
), or greater than or equal (>=
) to a constant value. Let’s pick up the question from the previous section. Let’s check if the obj
variable contains a Developer
and if the YearOfBirth
property is greater or equal to 1980. It’s as simple as this:
if (obj is Developer { YearOfBirth: >= 1980 } devBornInOrAfter1980)
{
// Use the devBornInOrAfter1980 variable here
}
But what if you want to check if the developer was born in the eighties. You can do that with Pattern Combinators, they were also introduced with C# 9.0.
C# 9.0: Pattern Combinators
C# 9.0 allows you to combine patterns with the pattern combinators and
and or
. You can also negotiate a pattern with the not
keyword. So, to check if the obj
variable contains a Developer
and if the YearOfBirth
property is greater or equal to 1980 and less or equal to 1989, you write it in your code exactly like I wrote it in this sentence:
if (obj is Developer { YearOfBirth: >= 1980 and <= 1989 } devBornInEighties)
{
// Use the devBornInEighties variable here
}
Note that the two relational patterns >=
and <=
are combined in the code snippet above by using the and
keyword. Now, what if you want to exclude developers born in 1984? You just add that check with the keywords and
and not
like you see it in the snippet below.
if (obj is Developer { YearOfBirth: >= 1980 and <= 1989 and not 1984 } devBornInEighties)
{
// Use the devBornInEighties variable here
}
So far we always assumed that we don’t have a variable of type Developer
, but a variable of type object
. In the code snippet above you see after the is
keyword the type Developer
. That is the Type Pattern, as it ensures that the object is of type Developer
. Then the curlies open to check the YearOfBirth
property, that is the Property Pattern.
If you have already a variable of type Developer
, you can omit the Type Pattern, and you use the Property Pattern directly after the is
keyword. Look at the code below, there is a developer
variable and the is
pattern expression is used with a Property Pattern to check if the dev is born in the eighties, but not in 1984.
var developer = new Developer { YearOfBirth = 1983 };
if (developer is { YearOfBirth: >= 1980 and <= 1989 and not 1984 })
{
// The dev is born in the eighties, but not in 1984
}
I am a huge fan of the syntax above. The alternative syntax that works with older C# versions is the classic one below. It looks quite noisy compared to the combined patterns that you see in the snippet above:
if (developer != null
&& developer.YearOfBirth >= 1980
&& developer.YearOfBirth <= 1989
&& developer.YearOfBirth != 1984)
{
// The dev is born in the eighties, but not in 1984
}
As mentioned already before, patterns are also quite powerful when you nest them. Do you want to check if a dev’s manager is born in the eighties, but not in 1984? Nothing simpler than that:
if (developer is { Manager: { YearOfBirth: >= 1980 and <= 1989 and not 1984 } })
{
// The dev's manager is born in the eighties, but not in 1984
}
Do you want to get developers that were not born in the eighties? Use the or
pattern combinator to check if the year of birth is less than 1980 or greater than 1989:
if (developer is { YearOfBirth: < 1980 or > 1989 })
{
// The dev is not born in the eighties
}
Important to know is that you can use the is
pattern expression and relational patterns with pattern combinators also directly on a value, which means you don’t need any curlies. In the snippet below I use the is
pattern expression directly on the value of the YearOfBirth
property. But there’s an important difference between the snippet below and the snippet above. The snippet above uses the Property Pattern, and that will also work fine if the developer
variable is null
. Then the pattern does not match and the if block is not entered. The snippet below will throw a NullReferenceException
if the developer
variable is null
, as we access its YearOfBirth
property with the dot syntax. So, I would prefer the approach above with the Property Pattern.
if (developer.YearOfBirth is < 1980 or > 1989)
{
// the dev is not born in the eighties
}
But if you have already an integer variable, then the approach to use relational patterns and pattern combinators directly on the value is ok. If you have for example a yearOfBirth
variable of type int
, you can do something like you see it in the if statement below:
if (yearOfBirth is < 1980 or > 1989)
{
// the yearOfBirth is not in the eighties
}
Use the var Pattern to Create Variables
Sometimes you might want to store property values in a variable if the other patterns are matching. Let’s say you need to get the manager of developers born in the eighties, but you don’t need the developers as such. You can do this with the var
pattern as shown in the code snippet below. The Manager
object is stored in a manager
variable that you can use in the if
block.
if (obj is Developer { YearOfBirth: >= 1980 and <= 1989, Manager: var manager })
{
// Use the manager variable here
}
The var pattern is not new in C# 9.0, but I thought it’s worth to mention this possibility here. You can also have mutliple variable declarations. The code snippet below creates in addition a variable for the firstname of the developer.
if (obj is Developer { YearOfBirth: >= 1980 and <= 1989, Manager: var manager, FirstName: var devFirstName })
{
// Use the manager variable and the devFirstName variable here
}
C# 9.0: Pattern Combinators and Null Checks
The is
pattern expression and the not
keyword allows you to write null
checks and not null checks in C# 9.0 in a more elegant way than ever before. It’s like this:
if (developer is null) { }
if (developer is not null) { }
You can read more about the history of null checks in C# in this blog post.
Wait Thomas, Now We Have and and && ?
Yes, exactly, but they are used for different things:
- The
and
pattern combinator is used to combine patterns. - The conditional and operator
&&
is a boolean operator and it is used to combine bool values in your C# code.
For example, you can write this, which is totally valid, as you use &&
to combine two bool values:
bool isTrue = true && true;
But you cannot compile the code that you see in the code snippet below, as you’re combining two bool values, but you’re not combining patterns. This means the and
keyword is here not valid.
bool isTrue = true and true; // Does not compile
Also the other way around, if you try to combine patterns with &&
instead of and
, your code won’t compile:
if (developer is { YearOfBirth: >= 1980 && <= 1989 }) // Does not compile
{
}
Now, the interesting point is that the is
pattern expression evaluates to a bool
value. Of course it does, else I couldn’t have used it in this blog post for the condition of an if
statement . This means in other words that you can assign the result of the is
pattern expression to a bool
variable like below:
bool isDevBornInTheEighties = obj is Developer { YearOfBirth: >= 1980 and <= 1989};
That means you can use the bool
operators &&
and ||
to combine different bool values, as you always did in C#. The is
pattern expression is a piece of code that evaluates to bool
, and it can contain patterns that are combined with and
and or
. Look at the snippet below where you can see both in action. Note how &&
is used to combine bool values and how and
is used to combine the two relational patterns >= 1980
and <= 1989
.
bool shouldLog = true;
if (shouldLog && developer is { YearOfBirth: >= 1980 and <= 1989 })
{
Console.WriteLine("Dev born in the eighties");
}
I hope this makes the difference between &&
and and
clear for you.
Exactly the same rules apply to the conditional or operator ||
and to the pattern combinator or
. Use ||
to combine bool values, use or
to combine patterns.
Summary
In this blog post you learned about using patterns with the is
pattern expression. The relational patterns are a powerful new feature of C# 9.0, and the pattern combinators and
and or
allow you to combine patterns to check if an object has a specific shape.
You learned in this blog post that the is
pattern expression allows you to evaluate patterns. But you can also evaluate patterns in a switch
expression. A switch
expression can also use all the C# 9.0 magic that you’ve seen in this blog post with relational patterns and pattern combinators.
In the next blog post you will learn about patterns in switch
expressions. Don’t worry if you’re not familiar with switch
expressions yet. You will learn how C# evolved to support not only simple switch
statements, but also powerful switch
expressions.
Comments (10)
This is by far the best explanation of this topic I’ve ever read! Thank you very much!
Thank you Rene, I’m happy to read that this post was helpful for you. – Thomas
Your blog post is most excellent. Truly clear and concise. I am really liking the direction they are taking C#. The code you displayed here is very readable and very elegant. I can’t wait to start using it to write new code or while refactoring old code. C# is becoming as fun to use as the old BASIC of the eighties used to be, only in an object-oriented way.
Thank you David, happy to read that you like the blog post. I think so too, the features added with C# 9.0 allow us to write more elegant code, and I also like the direction in which the language evolves.
Your explanation blow up my mind on how to write C# code. As a fresh graduate that never took any courses that teach or using C# in university and start a career as a software developer that working on developing on ASP.NET 5 project. After working half year on writing C# code, when I see this topic really change my mindset about how to writing the code. Never thought that, the code can be written like this. Thanks a lot and please keep on sharing the easy readable and maintainable code article on C#.
Thank you, happy to read you like the blog post. thomas
Thanks for the very interesting and informative explanation.
There is however one important thing you forgot to mention: it only works with constants on the right side of the “is” :/
Let’s hope the .Net team will open it to variables someday, because the shortening of multiple checks on the same variable is much more readable with this feature.
Perfect and full explanation, which even answers the upcoming ‘what if else’ questions, w/o leaving you allone with them.
Hey , Thanks for your post.
How can I improve this line of code?
return resp != null && resp.ItemExists && (!resp.Value.IsAssigned || resp.IsUserParam);
I assume I can get rid of the first && somehow! any help would be appreciated.
Hi Ehsan,
you could write this completely as an expression:
return resp is not null and { ItemExists: true }
and ({ Value.IsAssigned: false } or { IsUserParam: true });
But in my opinion, it doesn’t get more readable in this case.
The cases where it really increases readability are these like this here:
var isCustomerOrOrder = resp.ParameterTypeName !=null
&& (resp.ParameterTypeName == “Customer”
|| resp.ParameterTypeName == “Order”);
You could write this one like this:
var isCustomerOrOrder = resp is { ParameterTypeName: “Customer” }
or { ParameterTypeName: “Order” };