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.

Share this post

Comments (6)

  • Rene Riedinger Reply

    This is by far the best explanation of this topic I’ve ever read! Thank you very much!

    February 18, 2021 at 8:11 pm
    • Thomas Claudius Huber Reply

      Thank you Rene, I’m happy to read that this post was helpful for you. – Thomas

      February 18, 2021 at 10:31 pm
  • David Lindenmuth Reply

    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.

    February 19, 2021 at 4:17 am
    • Thomas Claudius Huber Reply

      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.

      February 19, 2021 at 8:37 am
  • JS Reply

    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#.

    March 12, 2021 at 3:08 am

Leave a 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.