In the development, code must be readable and straightforward. Pattern matching is one of the most powerful features introduced in recent C# versions, and it can revolutionize the way you write conditional logic. Using pattern matching, you can simplify complex code, reduce bugs and make it easy to maintain code.
In this blog, we’ll explore the different types of pattern matching in C#, break down how each works, and provide practical examples to help you master this essential tool. Let’s dive in!
What Is Pattern Matching in C#?
Pattern matching allows you to test data against a specific pattern and simplifies decision-making in your code. It’s more than just a switch statement replacement; it introduces new ways to handle data types, structures, and conditions.
Benefits of Pattern Matching in C#
- Readable: No more sprawling if-else chains.
- Expressive: Match patterns directly, reducing boilerplate code.
- Flexible: Handle complex scenarios like deconstruction and recursion effortlessly.
Whether validating user inputs, categorizing logs, or applying business rules, pattern matching is your new best friend.
Types of Pattern Matching in C#
1. Type Patterns
Type patterns let you match the type of an object and use it directly without explicit casting.
using System;
object data = "Hello, dotnet infinity!";
if (data is string strType)
{
Console.WriteLine($"String Type: {strType}");
}
if (data is int intType)
{
Console.WriteLine($"Integer Type: {intType}");
}
//Output
//String Type: Hello, dotnet infinity!
Here, the “is” keyword checks if the data is “String”, it will print first If block. If we change data to “Integer”, it will print the second If block.
If you want to test all c# and .Net code online, you can use dotnet Fiddle.
2. Relational Patterns
Relational patterns use comparison operators (<, >, <=, >=) to match values.
using System;
int age = 15;
string ageCategory = age switch
{
< 18 => "Minor",
<= 65 => "Adult",
_ => "Senior"
};
Console.WriteLine($"Age Category: {ageCategory}");
//Output
//Age Category: Minor
This approach makes categorization straightforward and eliminates repetitive if-else statements.
3. Logical Patterns
Logical patterns combine other patterns using and, or, and not operators.
Logical patterns are useful for combining complex conditions concisely, reducing code clutter and improving code readability.
using System;
int score = 90;
if (score is >= 70 and <= 90)
{
Console.WriteLine("Distinction.");
}
4. List Patterns (C# 11+)
List patterns enable matching elements in collections.
using System;
int[] numbers = { 11, 12, 13 };
if (numbers is [11, 12, 13])
{
Console.WriteLine("Exact match!");
}
else{
Console.WriteLine("Not match!");
}
//Output
//Exact match!
using System;
int[] numbers = { 11, 12, 13, 14 };
if (numbers is [11, 12, 13])
{
Console.WriteLine("Exact match!");
}
else{
Console.WriteLine("Not match!");
}
//Output
//Not match!
You can also match partial lists or use discard (_) for unspecified elements:
using System;
int[] numbers = { 11, 12, 13, 14 };
if (numbers is [11,_,_,14])
{
Console.WriteLine("Starts with 11 and ends with 14.");
}
//Output
//Starts with 11 and ends with 14.
5. Constant Patterns
Constant patterns match against specific constant values.
using System;
string role = "Manager";
switch (role)
{
case "Admin":
Console.WriteLine("Access granted.");
break;
case "Manager":
Console.WriteLine("Limited access.");
break;
case "User":
Console.WriteLine("Limited access.");
break;
default:
Console.WriteLine("Access denied.");
break;
}
//Output
//Limited access.
6. Positional Patterns
Positional patterns allow you to deconstruct objects directly within a match.
using System;
var point = (X: 12, Y: 26);
switch (point)
{
case (0, 0):
Console.WriteLine("Origin");
break;
case (_, 26):
Console.WriteLine("On the horizontal line at Y=26");
break;
default:
Console.WriteLine("Somewhere else");
break;
}
//Output
// On the horizontal line at Y=26
7. Property Patterns
Property patterns match object properties.
using System;
var customer = new { Type = "VIP", Orders = 12 };
if (customer is { Type: "VIP", Orders: >= 5 })
{
Console.WriteLine("Special discount applied.");
}
else{
Console.WriteLine("Normal discount applied.");
}
//Output
//Special discount applied.
8. Recursive Patterns
Recursive patterns handle nested data structures by combining property and positional patterns. They are particularly useful for working with hierarchical or deeply nested objects.
using System;
using System.Collections.Generic;
class Employee
{
public string Name { get; set; }
public string Role { get; set; }
public List<Employee>? Subordinates { get; set; }
}
class Program
{
static void Main(string[] args)
{
// Create an employee hierarchy
var ceo = new Employee
{
Name = "Alice",
Role = "CEO",
Subordinates = new List<Employee>
{
new Employee
{
Name = "Bob",
Role = "Manager",
Subordinates = new List<Employee>
{
new Employee { Name = "Charlie", Role = "Developer" },
new Employee { Name = "Diana", Role = "Designer" }
}
},
new Employee
{
Name = "Eve",
Role = "Manager",
Subordinates = new List<Employee>
{
new Employee { Name = "Frank", Role = "Developer" }
}
}
}
};
// Check if the CEO has a manager named "Bob" with a subordinate named "Charlie"
if (ceo is { Subordinates: [{ Name: "Bob", Subordinates: [{ Name: "Charlie" }] }] })
{
Console.WriteLine("The CEO has a manager named Bob with a subordinate named Charlie.");
}
else
{
Console.WriteLine("The specified hierarchy was not found.");
}
}
}
//Output
// The specified hierarchy was not found.
9. Var Pattern
The var pattern matches any value and assigns it to a variable without specifying its type. This is especially useful when you want to capture a value for further use without additional conditions.
using System;
object name = "dotnet infinity";
if (name is var value)
{
Console.WriteLine($"Captured value: {value}");
}
//Output
//Captured value: dotnet infinity
10. Discard Pattern
The discard pattern ignores certain values in a match, using _ as a placeholder for values you don’t care about.
using System;
var point = (X: 15, Y: 20);
switch (point)
{
case (_, 20):
Console.WriteLine("Y-coordinate is 20.");
break;
default:
Console.WriteLine("Other point.");
break;
}
//Output
//Y-coordinate is 20.
11. Parenthesized Pattern
Parenthesized patterns group patterns to control the order of evaluation, especially when combining logical patterns.
using System;
int score = 89;
if (score is (>= 70 and <= 90))
{
Console.WriteLine("Distinction");
}
//Output
//Distinction
Real-World Examples
1. Determining Discounts for E-Commerce
using System;
class Program
{
public class Customer
{
public string Type { get; set; }
public int Orders { get; set; }
}
static decimal GetDiscount(Customer customer) => customer switch
{
{ Type: "VIP", Orders: >= 5 } => 0.2m,
{ Type: "Regular", Orders: >= 10 } => 0.1m,
_ => 0.0m
};
static void Main()
{
var vipCustomer = new Customer { Type = "VIP", Orders = 8 };
var regularCustomer = new Customer { Type = "Regular", Orders = 12 };
var newCustomer = new Customer { Type = "New", Orders = 1 };
Console.WriteLine($"VIP Customer Discount: {GetDiscount(vipCustomer):P}");
Console.WriteLine($"Regular Customer Discount: {GetDiscount(regularCustomer):P}");
Console.WriteLine($"New Customer Discount: {GetDiscount(newCustomer):P}");
}
}
//Output
//VIP Customer Discount: 20.00 %
//Regular Customer Discount: 10.00 %
//New Customer Discount: 0.00 %
2. Validating API Inputs
using System;
bool ValidateInput(object input) => input switch
{
string s when !string.IsNullOrEmpty(s) => true,
int i when i > 0 => true,
_ => false
};
Console.WriteLine(ValidateInput("Hello Dotnet")); // True
Console.WriteLine(ValidateInput(56)); // True
Console.WriteLine(ValidateInput(89.23)); // False
Complete Example: Pattern Matching in Action
In this section, you’ll see how to integrate multiple pattern-matching concepts into a cohesive solution. By combining property, constant, and type patterns, we can effectively categorize and process a collection of items in a real-world scenario. Here’s a practical demonstration:
using System;
using System.Collections.Generic;
public record Product(string Name, decimal Price, string Category);
class Program
{
static void Main()
{
var products = new List<Product>
{
new("Mobile", 18000, "Electronics"),
new("Banana", 12, "Groceries"),
new("T-Shirt", 35, "Apparel")
};
foreach (var product in products)
{
string message = product switch
{
{ Category: "Electronics", Price: > 1000 } => "High-value electronics.",
{ Category: "Groceries" } => "Essential item.",
_ => "General merchandise."
};
Console.WriteLine($"{product.Name}: {message}");
}
}
}
//Output
//Laptop: High-value electronics.
//Apple: Essential item.
//Shirt: General merchandise.
Key Takeaway: Pattern Matching
- Pattern matching simplifies complex conditional logic, making your code cleaner and more expressive.
- Use different pattern types to handle various scenarios, from simple type checks to complex nested data structures.
- Incorporate pattern matching in your projects to improve code readability and maintainability.
Now it’s your turn to master pattern matching! Start exploring these patterns in your C# projects and see the difference they can make.