I was happy to see that C# 7 introduced the first version of pattern matching, which was supporting the bare minimum. Because of my previous encounters with pattern matching, this was a bit of a let-down because it didn't have all the possibilities I was used to. Luckily, the release of C# 8 extended the pattern match syntax by supporting more features, which were further enhanced in C# 9, and it also seems like this trend is continuing in the upcoming versions of C#.
So why am I excited about this?
Everything that you can do with pattern matching is also possible without using it, but it won't look as good. Pattern matching can turn a complex if-else or switch statement into a compact block of code.
Because of this, my developer experience has improved with the addition of pattern matching. Its strength is that it's expressive, making it easier to read and harder to write bugs.
A bonus is that the compiler acts as a safety net (more on this later), it warns for unhandled cases and prevents cases to conflict with each other. This isn't something you get with a normal if-else statement, which sadly, has bitten me in the past. It's good to know that compiler has your back by reporting silly mistakes.
For the rest of this post, let's take a look at some examples of how pattern matching with switch expressions is used.
The simplest example is a check for a simple true/false value.
Pattern matching compares an input value (
false in the example below) to a set of defined patterns, think of them as a set of conditions. The patterns inside the switch expression are evaluated from top to bottom, and the first case that causes a match is executed.
We can use pattern matching to check if a variable has a constant value. The types that are matchable are strings, chars, numbers, and enums.
Because not every number is included in the switch expression of the previous example, the C# compiler warns that there are uncovered cases.
To address this warning, we can add a fallback case.
Think of it as a default case from a switch statement, a wildcard that is invoked when no other of the previous patterns are matched. In the example below, the discard operator (represented as an underscore
_) is used to match all other possible values.
A matched value can be assigned to a variable.
In the following example, the variable acts as a wildcard that matches any value.
A variable can also be defined when the input value of a Type Pattern matches a Type, the created variable is typed in this case. The variable can then be used in the execution expression (after the
=>) to create a return value.
when statement can be added to the pattern to add an extra guard to the pattern.
when statement can be used to match a non-constant value against a condition, for example, to invoke a method on the matched variable. Besides matching the object that is being matched, it's also possible to include other objects in the
We use relation operators (
<=) to test if the input is equal, greater, or less than another value.
Did you spot the mistake in the example? If that isn't the case, you don't have to worry, because this time the compiler throws an error to address my mistake.
Let's fix this mistake by correcting the pattern.
To combine patterns or to negate values, we use the logical operators (
Matching single values is nice but not very useful in many cases. To match multiple values, we can pattern match multiple input values by using tuples.
When the input is an object, we can add a pattern to the properties of an object.
It's even possible to match nested properties.
The above example can be hard to read (especially if you need to drill down into multiple objects). As a remedy, C# 10 provides a new syntax called extended property patterns to make this easier on the eyes. The refactored example looks as follows. Much better, right?
Pattern matching can also be used to match a type of an object. Type patterns are useful when you have a generic handler that acts as a pass-through.
The examples we've seen so far are simple and are here to show the different syntaxes to build a pattern. To unlock the true power of pattern matching, multiple pattern matching strategies can be combined.
We've only seen examples of pattern matching expressions in this post. For more details, please take a look at the official documentation:
- Pattern matching overview
- Use pattern matching to build your class behavior for better code
- Tutorial: Use pattern-matching to build type-driven and data-driven algorithms
Please consider supporting me if have you enjoyed this post and found it useful: