Pattern matching is a great feature first introduced in C# 7. You can use pattern matching on any data type, even on custom data types, to extract values from an expression.
Pattern matching was enhanced in C# 8, which introduced a wide array of new pattern types. It was enhanced even further in C# 9, with the addition of new relational and logical patterns. This article talks about the newly added patterns in C# 9 and how we can take advantage of them, with code examples wherever appropriate.
To work with the code examples provided in this article, you should have Visual Studio 2019 installed in your system. If you don’t already have a copy, you can download Visual Studio 2019 here.
Create a console application project in Visual Studio
First off, let’s create a .NET Core console application project in Visual Studio. Assuming Visual Studio 2019 is installed in your system, follow the steps outlined below to create a new .NET Core console application project in Visual Studio.
- Launch the Visual Studio IDE.
- Click on “Create new project.”
- In the “Create new project” window, select “Console App (.NET Core)” from the list of templates displayed.
- Click Next.
- In the “Configure your new project” window, specify the name and location for the new project.
- Click Create.
We’ll use this project to explore the new pattern matching features in C# 9 in the subsequent sections of this article.
Relational patterns in C# 9
Relational patterns enable the use of the relational operators less than <
, less than or equal to <=
, greater than >
, and greater than or equal to >=
.
Consider the following class named Employee.
public class Employee
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public double Basic { get; set; }
public double Tax { get; set; }
public double NetSalary { get; set; }
}
The following code snippet illustrates how you can take advantage of relational patterns to calculate the tax rate for an employee.
private static double GetTaxPercentage(Employee employee) => employee.Basic switch
{
<= 1000 => 5.00,
<= 5000 => 10.00,
<= 10000 => 20.00,
_ => 30.00
};
The following code snippet shows how you can create an instance of the Employee class, initialize its data members, and display the percentage of tax (calculated using the code snippet shown above) at the console.
static void Main(string[] args)
{
Employee employee = new Employee { Id = 1, FirstName = "Joydip", LastName = "Kanjilal", Basic = 8000 };
Console.WriteLine("The percentage of income tax is: " + GetTaxPercentage(employee));
Console.Read();
}
When you execute the above program, the following output will display in the console window:
The percentage of income tax is: 20
Logical patterns in C# 9
Logical patterns enable you to use the logical operators and
, or
, and not
. Note that you can use logical patterns and relational patterns independently or in combination.
The addition of conjunctive, disjunctive, and negation patterns makes pattern matching in C# much more flexible. While the and
keyword requires the patterns on both sides of the conjunction to match, the or
keyword requires only one of the patterns to match. Naturally, the not
keyword is used to represent negation and requires that the pattern does not match the specified condition.
The following code snippet — a rewrite of the GetTaxPercentage method — illustrates how logical patterns can be combined with relational patterns in C# 9.
private static double GetTaxPercentage(Employee employee) => employee.Basic switch
{
<= 1000 => 5.00,
> 1000 and <= 5000 => 10.00,
> 5000 and <= 10000 => 20.00,
_ => 30.00
};
Negation patterns
The not pattern is yet another pattern that takes advantage of the not
logical operator and can be used in an if
construct or even with a ternary statement.
Consider the following class named RetiredEmployee that extends the Employee class and adds two data members.
public class RetiredEmployee: Employee
{
public bool IsRetired { get; set; }
public double Pension { get; set; }
}
The following code snippet illustrates how the negation pattern can be used.
private static bool IsRetired(Employee employee)
{
if (employee is not RetiredEmployee)
return false;
return true;
}
You can call the above method from the Main function as shown below:
static void Main(string[] args)
{
Employee employee = new RetiredEmployee { Id = 1, FirstName = "Joydip",
LastName = "Kanjilal", IsRetired = true, Pension = 10000 };
Console.WriteLine(IsRetired(employee)? "Retired...":"Not retired...");
Console.Read();
}
When you execute the above program, you should see the following output in the console window:
Retired...
Conjunctive and disjunctive patterns
The conjunctive and disjunctive patterns introduced in C# 9 are so named because they rely on the logical operators for conjunction and disjunction, and
and or
. While and
requires both patterns to match, or
requires just one of the patterns to match.
The following code snippet illustrates how you can use and
logic on two different patterns.
if (employee.Tax is double and >= 10)
{
// This code block will be executed if
// Tax property is of type double and
// the value of tax is 10 or greater
Console.WriteLine("The monthly income tax: 10%");
}
Note that the preceding code snippet combines the conjunctive and relational patterns and combines comparisons of types and properties.
And the following code snippet shows how the disjunctive or
can be used to execute logic on either of two different matches.
if (employee.Tax is (10 or 20) and int tax)
{
// This code block will be executed if the value of tax is 10 or 20
}
Pattern matching can help you write code that is more readable, maintainable, and efficient. Because pattern matching makes it easier to traverse complex object structures, you will find it quite useful to include in the if
and switch
statements in your code. C# 9.0 introduces several enhancements to pattern matching over its earlier incarnations.
Finally, it should be noted that pattern matching works with constants only. If you try to use a variable or an object in lieu of a constant in a pattern matching expression, you will be greeted with the following error:
// Compiler error CS0150: A constant value is expected
How to do more in C#:
- How to work with read-only collections in C#
- How to work with static anonymous functions in C# 9
- How to work with record types in C#
- How to use implicit and explicit operators in C#
- Singleton vs. static classes in C#
- How to log data to the Windows Event Log in C#
- How to use ArrayPool and MemoryPool in C#
- How to use the Buffer class in C#
- How to use HashSet in C#
- How to use named and optional parameters in C#
- How to benchmark C# code using BenchmarkDotNet
- How to use fluent interfaces and method chaining in C#