Last Updated on September 13, 2023 by Aram
Pattern matching in C# is a feature used to test expressions for some conditions while testing their types.
Generally, Pattern matching is a functional programming feature that already exists in other popular languages such as Scala, Rust, Python, Haskell, Prolog and many other languages.
Pattern Matching was introduced in C# 7, and ever since it has been receiving many updates over the subsequent major releases
This guide will help you learn more about the types of pattern matching with a usage example for each type.
Remember, you can only apply pattern matching within either ‘is’ expressions or switch expressions.
Benefits of Pattern matching in C#
- Type-Testing
- Nullable-type checking
- Type casting and assignment
- High readability
- Concise syntax
- Less convoluted code
So let’s get started with the different types of pattern matching in C#
C# 7
Type Pattern
Type-testing for the expression
1 2 3 4 |
public bool IsProductFood(object product) { return product is FoodModel; } |
Declaration Pattern
Type-testing as well as assignment to variable after a successful match of the expression
1 2 3 4 5 |
public bool IsProductFoodThatRequiresRefrigeration(object product) { return product is FoodModel food && RequiresRefrigeration(food.StorageTemperature); } |
Constant Pattern
Testing versus a constant value which can include int, float, char, string, bool, enum, field declared with const, null
1 2 3 4 |
public bool IsFreshProduce(FoodModel food) { return food?.Category?.ID is (int) ProductCategoryEnum.FreshProduce; } |
Null Pattern
Check if a reference or nullable type is null
1 2 3 4 |
public bool FoodDoesNotExist(FoodModel food) { return food is null; } |
Var Pattern
Similar to the type pattern, the var pattern matches an expression and checks for null, as well as assigns a value to the variable.
The var type is declared based on the matched expression’s compile-time type.
1 2 3 4 5 |
public bool IsProductFoodThatRequiresRefrigeration(FoodModel food) { return GetFoodStorageRequirements(food) is var storageRequirement && storageRequirement is StorageRequirementEnum.Freezer; } |
C# 8
Property Pattern
It is a highly usable type of pattern matching where you can incorporate object members rather than variables to match the given conditions.
It can be easily used with the other pattern matching types, such as the relative pattern, pattern combinators and many other patterns to build flexible and powerful logical expressions.
1 2 3 4 5 6 7 8 9 |
public bool IsOrganicFood(FoodModel food) { return food is { NonGMO: true, NoChemicalFertilizers: true, NoSyntheticPesticides: true }; } |
Usage of nested properties was available in C# 8 along with C# 9, but implementing it was not the cleanest solution or the most concise syntax.
Discard Pattern
Pattern matching with the discard operator _ matches anything including null, its use shines in the new switch expressions, to match the default or else case. In the below example, if the food’s storage temperature was not provided or the number doesn’t match the below ranges, then a custom exception will be thrown:
1 2 3 4 5 6 7 |
public int GetFoodStorageTemperature(StorageRequirementEnum storageRequirement) => storageRequirement switch { StorageRequirementEnum.Freezer => -18, StorageRequirementEnum.Refrigerator => 4, StorageRequirementEnum.RoomTemperature => 25, _ => throw new InvalidStorageRequirementException() }; |
Positional Pattern
Mainly used with struct types, leverages a type’s deconstrutor to match a pattern according to the values position in the deconstructor
Notice we are also using the discard pattern to ignore the value of currency, since anything that has zero value, regardlress of the currency, should be free.
1 2 3 4 |
public bool IsFreeFood(FoodModel food) { return food.Price is (0, _); } |
Tuple Pattern
A special derivation from the positional pattern where you can test multiple properties of a type in the same expression
1 2 3 4 5 6 7 |
public string GetFoodDescription(FoodModel food) => (food.NonGMO, food.Category.ID) switch { (true, (int)ProductCategoryEnum.FreshProduce) => "Non-GMO Fresh Product", (true, (int)ProductCategoryEnum.Dairy) => "Non-GMO Dairy", (false, (int)ProductCategoryEnum.Meats) => "GMO Meats. Avoid!", (_, _) => "Invalid Food Group" }; |
C# 9
‘Enhanced’ Type Pattern
You can do type checking in switch expressions without using the discards with each type
1 2 3 4 5 6 7 8 9 10 11 12 |
public string CheckValueType(object value) => value switch { int => "This is an integer number", decimal => "This is a decimal number", double => "This is a double number", _ => throw new InvalidNumberException(value) }; public class InvalidNumberException : Exception { public InvalidNumberException(object value) : base($"Invalid number {value}") { } } |
Relational Pattern
Introduced in C# 9, a relational pattern allows applying the relational operators > < >= <= to match patterns versus constants or enum values
1 2 3 4 5 6 7 |
public StorageRequirementEnum GetFoodStorageRequirements(FoodModel food) => food.StorageTemperature switch { <= -18 => StorageRequirementEnum.Freezer, >= 2 and < 6 => StorageRequirementEnum.Refrigerator, > 6 and < 30 => StorageRequirementEnum.RoomTemperature, _ => throw new InvalidStorageRequirementException(food.StorageTemperature) }; |
Logical Pattern
This represents the set of negation, conjunctive, disjunctive; not, and, or respectively. Together these are called the pattern combinators
These are used to combine patterns and apply logical conditions on them
1 2 3 4 |
public bool RequiresRefrigeration(ProductCategoryEnum productType) { return productType is ProductCategoryEnum.Dairy or ProductCategoryEnum.Meats; } |
Negated Null Constant Pattern
Checks expression for not null value
1 2 3 4 |
public bool BlogExists(BlogModel blog) { return blog is not null; } |
Parenthesized Pattern
This allows the use of parenthesis to control the order of execution and to group logical expressions together. It can be used with any type of pattern, but its usage is mainly associated with the use of pattern combinators
1 2 3 4 |
public bool RequiresRefrigeration(int storageTemperature) { return storageTemperature is > 1 and (< 6); } |
C# 10
Extended Property Pattern
In C# 10, the nested properties matching syntax issue was addressed.
With the introduction of the Extended Property Pattern, syntax to use nested properties in pattern matching is now clean and concise.
1 2 3 4 5 6 7 8 |
public bool RequiresRefrigeration(FoodModel food) { return food is { Category.ID: (int)ProductCategoryEnum.Dairy or (int)ProductCategoryEnum.Meats }; } |
C# 11
List Pattern
The list matching pattern is the latest addition to the great set of pattern matching in C#, with list pattern you can match a list or an array with a set of sequential elements
You can mix it with discard, pattern combinators, range, var, type assignemnt patterns to build very flexible and powerful list pattern matching
1 2 3 4 5 6 7 8 9 10 |
public (int?, int?) FindNumberOneAndNumberFour() { int[] numbers = { 1, 2, 3, 4, 5 }; // Match if 2nd value is anything, 3rd is greater than or equal 3, fifth is 5 if (numbers is [var numberOne, _, >= 3, int numberFour, 5]) { return (numberOne, numberFour); } return (null, null); } |
Too Long; Didn’t Read
Summary
During the recent few years, C# has been receiving lots of language features to help developers build solid and reliable application on a diverse range of platforms.
With the addition of the pattern matching features, C# has added a great functional programming capability that has been being used for decades throughout a multitude of programming languages.
Of course, you always need to remain cautious not to over-use the pattern matching.
Mainly if you are working on a large project with other team members, remember that not everyone would be aware about some or all of the pattern matching features, so you don’t want to suddenly introduce too many of these.
This article gave a quick and complete overview of all the pattern matching types added in C#.
Let me know if you found it useful and if you have any comment.
References
To learn more about Pattern Matching in C#, you can check these references:
https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/functional/pattern-matching
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/patterns
https://code-maze.com/csharp-pattern-matching/
.NET Learning Resources
As part of my learning journey I always try to assign some time to write and share content with anyone who is interested to learn.
Please feel free to connect with me LinkedIn, I do regularly share content mainly in .NET and C#.
Also you can always read any of my tutorials in this blog, here are some examples:
- Apply JWT Access Tokens and Refresh Tokens in ASP.NET Core Web API
- Swagger OpenAPI Configurations in ASP.NET Core Web API
- File Upload with Data using ASP.NET Core Web API
- Logging with Serilog in ASP.NET Core Web API
- Localization in ASP.NET Core Web API
- Secure Angular Site using JWT Authentication with ASP.NET Core Web API
Collaborations and Sponsorships
If you want to learn about the available opportunities to collaborate and work together, please check this page:
https://codingsonata.com/sponsorships-and-collaborations/
Bonus
Enjoy the romantic piano tunes of the brilliant classical/romantic era music virtuoso, Frédéric Chopin.
Chopin, Waltz in A minor, B 150, Op. Posth
Very nice article! One notice: var-pattern doesn’t check the null (o is var stillNullableValue), it does object-pattern (o is { } nonNullValue)