Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / DevOps / testing

Your Quick Guide to Pattern Matching in C#

5.00/5 (16 votes)
11 Sep 2023CPOL4 min read 14.4K  
More about pattern matching types with a usage example for each type
Pattern matching in C# is a versatile feature for testing expressions based on conditions and types, offering benefits like type-testing, nullable-type checking, type casting, and high readability through various pattern types introduced in different C# versions. This guide will help you learn more about the types of pattern matching with a usage example for each type.

Last Updated on September 11, 2023

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:

C#
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:

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

C#
public bool IsFreshProduce(FoodModel food)
{
    return food?.Category?.ID is (int) ProductCategoryEnum.FreshProduce;
}

Null Pattern

Check if a reference or nullable type is null.

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

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

C#
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:

C#
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, regardless of the currency, should be free.

C#
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:

C#
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:

C#
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:

C#
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:

C#
public bool RequiresRefrigeration(ProductCategoryEnum productType)
{
    return productType is ProductCategoryEnum.Dairy or ProductCategoryEnum.Meats;
}

Negated Null Constant Pattern

Checks expression for not null value:

C#
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:

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

C#
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 assignment patterns to build very flexible and powerful list pattern matching:

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

Pattern Matchingi in C#

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:

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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)