Pattern Matching Overview
Pattern Matching Overview
Article • 12/04/2022
This article provides an overview of scenarios where you can use pattern matching.
These techniques can improve the readability and correctness of your code. For a full
discussion of all the patterns you can apply, see the article on patterns in the language
reference.
Null checks
One of the most common scenarios for pattern matching is to ensure values aren't
null . You can test and convert a nullable value type to its underlying type while testing
C#
The preceding code is a declaration pattern to test the type of the variable, and assign it
to a new variable. The language rules make this technique safer than many others. The
variable number is only accessible and assigned in the true portion of the if clause. If
you try to access it elsewhere, either in the else clause, or after the if block, the
compiler issues an error. Secondly, because you're not using the == operator, this
pattern works when a type overloads the == operator. That makes it an ideal way to
check null reference values, adding the not pattern:
C#
The preceding example used a constant pattern to compare the variable to null . The
not is a logical pattern that matches when the negated pattern doesn't match.
Type tests
Another common use for pattern matching is to test a variable to see if it matches a
given type. For example, the following code tests if a variable is non-null and
implements the System.Collections.Generic.IList<T> interface. If it does, it uses the
ICollection<T>.Count property on that list to find the middle index. The declaration
pattern doesn't match a null value, regardless of the compile-time type of the variable.
The code below guards against null , in addition to guarding against a type that doesn't
implement IList .
C#
The same tests can be applied in a switch expression to test a variable against multiple
different types. You can use that information to create better algorithms based on the
specific run-time type.
Compare discrete values
You can also test a variable to find a match on specific values. The following code shows
one example where you test a value against all possible values declared in an
enumeration:
C#
C#
The preceding example shows the same algorithm, but uses string values instead of an
enum. You would use this scenario if your application responds to text commands
instead of a regular data format. Starting with C# 11, you can also use a Span<char> or a
ReadOnlySpan<char> to test for constant string values, as shown in the following sample:
C#
In all these examples, the discard pattern ensures that you handle every input. The
compiler helps you by making sure every possible input value is handled.
Relational patterns
You can use relational patterns to test how a value compares to constants. For example,
the following code returns the state of water based on the temperature in Fahrenheit:
C#
The preceding code also demonstrates the conjunctive and logical pattern to check that
both relational patterns match. You can also use a disjunctive or pattern to check that
either pattern matches. The two relational patterns are surrounded by parentheses,
which you can use around any pattern for clarity. The final two switch arms handle the
cases for the melting point and the boiling point. Without those two arms, the compiler
warns you that your logic doesn't cover every possible input.
The preceding code also demonstrates another important feature the compiler provides
for pattern matching expressions: The compiler warns you if you don't handle every
input value. The compiler also issues a warning if a switch arm is already handled by a
previous switch arm. That gives you freedom to refactor and reorder switch expressions.
Another way to write the same expression could be:
C#
The key lesson in this, and any other refactoring or reordering, is that the compiler
validates that you've covered all inputs.
Multiple inputs
All the patterns you've seen so far have been checking one input. You can write patterns
that examine multiple properties of an object. Consider the following Order record:
C#
The preceding positional record type declares two members at explicit positions.
Appearing first is the Items , then the order's Cost . For more information, see Records.
The following code examines the number of items and the value of an order to calculate
a discounted price:
C#
The first two arms examine two properties of the Order . The third examines only the
cost. The next checks against null , and the final matches any other value. If the Order
type defines a suitable Deconstruct method, you can omit the property names from the
pattern and use deconstruction to examine properties:
C#
The preceding code demonstrates the positional pattern where the properties are
deconstructed for the expression.
List patterns
You can check elements in a list or an array using a list pattern. A list pattern provides a
means to apply a pattern to any element of a sequence. In addition, you can apply the
discard pattern ( _ ) to match any element, or apply a slice pattern to match zero or more
elements.
List patterns are a valuable tool when data doesn't follow a regular structure. You can
use pattern matching to test the shape and values of the data instead of transforming it
into a set of objects.
Consider the following excerpt from a text file containing bank transactions:
Output
It's a CSV format, but some of the rows have more columns than others. Even worse for
processing, one column in the WITHDRAWAL type has user-generated text and can contain
a comma in the text. A list pattern that includes the discard pattern, constant pattern and
var pattern to capture the value processes data in this format:
C#
The preceding example takes a string array, where each element is one field in the row.
The switch expression keys on the second field, which determines the kind of
transaction, and the number of remaining columns. Each row ensures the data is in the
correct format. The discard pattern ( _ ) skips the first field, with the date of the
transaction. The second field matches the type of transaction. Remaining element
matches skip to the field with the amount. The final match uses the var pattern to
capture the string representation of the amount. The expression calculates the amount
to add or subtract from the balance.
List patterns enable you to match on the shape of a sequence of data elements. You use
the discard and slice patterns to match the location of elements. You use other patterns
to match characteristics about individual elements.
This article provided a tour of the kinds of code you can write with pattern matching in
C#. The following articles show more examples of using patterns in scenarios, and the
full vocabulary of patterns available to use.
See also
Use pattern matching to avoid 'is' check followed by a cast (style rules IDE0020 and
IDE0038)
Exploration: Use pattern matching to build your class behavior for better code
Tutorial: Use pattern matching to build type-driven and data-driven algorithms
Reference: Pattern matching