Meta Description:
Learn the evolution of pattern matching in C# with clear explanations and detailed code examples. Explore type, declaration, constant, relational, logical, property, and more pattern types for writing cleaner, more maintainable code.
Introduction
Welcome to our in-depth guide on pattern matching in C#! Pattern matching is a feature that helps you write clearer and more concise code by checking the structure and type of objects. Originally introduced in C# 7, it has evolved to include many powerful patterns that make code easier to manage.
In this article, we'll walk you through different pattern types in C#, each accompanied by clear explanations and full example code.
What is Pattern Matching?
Pattern matching is like asking questions about an object:
- What type is it?
- What properties does it have?
- Does it meet certain conditions?
Before pattern matching, you might have written complex if-else blocks or switch statements to handle these checks. With pattern matching, the same logic can be expressed in a cleaner, more understandable way.
Evolution of Pattern Matching in C
Pattern matching has improved over the years, adding more types and patterns with each C# version. The major types of patterns include:
- Type Patterns
- Declaration Patterns
- Constant Patterns
- Relational Patterns
- Logical Patterns
- Property Patterns
- Positional Patterns
- Var Patterns
- Discard Patterns
- Parenthesized Patterns
Let’s explore each pattern type with code examples!
1. Type Patterns
Type patterns allow you to check the type of an object and assign it to a local variable.
Example
object obj = "Hello, World!";
if (obj is string message)
{
Console.WriteLine($"Message length: {message.Length}");
}
In this code:
-
obj
is checked to see if it's of type string. - If true, obj is assigned to the variable message, allowing you to access the Length property directly.
2. Declaration Patterns
Declaration patterns combine type checking with variable declaration.
Example
object data = 100;
if (data is int number)
{
Console.WriteLine($"Number is: {number}");
}
In this example:
- If data is an integer, it is assigned to number, making it easy to use in the conditional block.
3. Constant Patterns
Constant patterns check whether a value matches a constant.
Example
int orderStatus = 1;
switch (orderStatus)
{
case 0:
Console.WriteLine("Order is pending.");
break;
case 1:
Console.WriteLine("Order is confirmed.");
break;
case 2:
Console.WriteLine("Order is shipped.");
break;
default:
Console.WriteLine("Unknown status.");
break;
}
Here:
- The switch statement checks if orderStatus matches any of the constants (0, 1, 2).
4. Relational Patterns
Relational patterns allow you to compare values using relational operators (<, >, <=, >=).
Example
int amount = 150;
if (amount is > 100)
{
Console.WriteLine("High-value order.");
}
In this code:
- It checks if amount is greater than 100 using the relational pattern.
5. Logical Patterns
Logical patterns combine multiple patterns using logical operators like and (&&
), or (||
), and not (!
).
Example
int age = 25;
if (age is > 18 && age is < 60)
{
Console.WriteLine("Eligible for the program.");
}
Here:
- The code checks if age is between 18 and 60 using the and operator.
6. Property Patterns
Property patterns check specific properties of an object.
Example
var order = new { Status = "Shipped", Amount = 200 };
if (order is { Status: "Shipped", Amount: > 150 })
{
Console.WriteLine("High-value shipped order.");
}
In this code:
- The property pattern checks if Status is "Shipped" and Amount is greater than 150.
7. Positional Patterns
Positional patterns deconstruct objects and match based on the position of their components.
Example
var point = (3, 4);
if (point is (3, 4))
{
Console.WriteLine("Point is at (3, 4).");
}
Here:
- The positional pattern checks if the tuple point matches the values 3 and 4.
8. Var Patterns
Var patterns match any value and assign it to a variable, making them useful when you want to handle an object without knowing its type.
Example
object obj = 123;
if (obj is var value)
{
Console.WriteLine($"Matched value: {value}");
}
In this example:
- The var pattern matches obj and assigns it to value, regardless of its type.
9. Discard Patterns
The discard pattern ignores values that are not needed, often used to skip certain fields in deconstruction.
Example
var person = ("John", 30);
if (person is (_, 30))
{
Console.WriteLine("Person is 30 years old.");
}
In this code:
- The discard pattern ignores the first value and only checks if the second value is 30.
10. Parenthesized Patterns
Parenthesized patterns allow you to group patterns for clarity.
Example
int score = 75;
if (score is (> 50 and < 100))
{
Console.WriteLine("Score is between 50 and 100.");
}
Here:
- The parenthesized pattern groups the conditions, making it clear that both checks are part of the same logic.
Complete Example: Combining Multiple Patterns
Let's create a more complex example using multiple pattern types together:
public class Order
{
public IShippingProvider Provider { get; set; }
public string Status { get; set; }
public int Amount { get; set; }
}
public class SwedishPostalServiceShippingProvider : IShippingProvider
{
public int FreightCost { get; set; }
}
var order = new Order
{
Provider = new SwedishPostalServiceShippingProvider { FreightCost = 120 },
Status = "Shipped",
Amount = 300
};
if (order is
{
Provider: SwedishPostalServiceShippingProvider { FreightCost: > 100 } swedishProvider,
Status: "Shipped",
Amount: > 250
})
{
Console.WriteLine("High-value, shipped order using Swedish provider.");
}
In this complete example:
- The property pattern checks properties within order.
- The type pattern checks if Provider is of type SwedishPostalServiceShippingProvider.
- The relational pattern checks if FreightCost and Amount meet the specified conditions.
Summary
Pattern matching in C# has come a long way since its introduction in C# 7. It helps you write more readable and maintainable code by allowing for clear type checks, property evaluations, and complex logic combinations. With different patterns available, you can handle various scenarios more efficiently.
Practice Assignments
To strengthen your understanding, try these exercises:
Here's a more detailed breakdown for each assignment:
1. Easy: Write Code That Checks if an Object is a String Using the Type Pattern
Objective
The goal here is to use the type pattern to determine whether an object is of type string. If it is, you’ll print the length of the string.
Solution
The type pattern is a straightforward way to check the type of an object and assign it to a local variable if the type matches. Here's the detailed code:
// Example object
object obj = "Hello, Pattern Matching!";
// Using the type pattern to check if obj is a string
if (obj is string message)
{
Console.WriteLine($"The object is a string with length: {message.Length}");
}
else
{
Console.WriteLine("The object is not a string.");
}
Explanation:
- The
is
operator checks if obj is of type string. - If true, it assigns the value to the variable message, allowing you to use it within the block.
- If the check fails, it moves to the else block, indicating that the object is not a string.
2. Medium: Use a Switch Expression to Categorize Orders Based on Their Status
Objective
This assignment involves using a switch expression to classify orders based on their status property. This will help you practice using the compact and modern way of handling pattern matching in C#.
Solution
Here, we’ll create an order object with a Status property and use a switch expression to categorize the order:
// Order class with Status property
public class Order
{
public string Status { get; set; }
}
// Example order
var order = new Order { Status = "Confirmed" };
// Using a switch expression to categorize the order status
string orderCategory = order.Status switch
{
"Pending" => "The order is still pending.",
"Confirmed" => "The order is confirmed.",
"Shipped" => "The order has been shipped.",
_ => "Unknown order status."
};
Console.WriteLine(orderCategory);
Explanation:
- The switch expression takes order.Status as input and matches it against different string values like "Pending", "Confirmed", and "Shipped".
- For each match, it returns a corresponding message.
- If none of the patterns match, it returns "Unknown order status." using the default pattern _.
3. Difficult: Write Code That Uses Recursive Patterns to Check the Type and Properties of an Object
Objective
The goal is to use recursive patterns to check both the type of an object and its properties. Recursive patterns allow you to nest different types of patterns within one another.
Solution
Let’s assume we have an Order class with a Provider property that can be of type SwedishPostalServiceShippingProvider, and the Provider has a FreightCost property. We’ll use recursive patterns to check the type and properties.
// Define an interface and classes
public interface IShippingProvider { }
public class SwedishPostalServiceShippingProvider : IShippingProvider
{
public int FreightCost { get; set; }
}
public class Order
{
public IShippingProvider Provider { get; set; }
public string Status { get; set; }
public int Amount { get; set; }
}
// Example order object
var order = new Order
{
Provider = new SwedishPostalServiceShippingProvider { FreightCost = 150 },
Status = "Shipped",
Amount = 300
};
// Using recursive patterns to check type and properties
if (order is
{
Provider: SwedishPostalServiceShippingProvider { FreightCost: > 100 } swedishProvider,
Status: "Shipped",
Amount: > 250
})
{
Console.WriteLine("High-value, shipped order using Swedish provider.");
}
else
{
Console.WriteLine("Order does not meet the criteria.");
}
Explanation:
- The code first checks if the order's Provider is of type SwedishPostalServiceShippingProvider and if the FreightCost is greater than 100.
- Then, it checks if the Status is "Shipped" and the Amount is greater than 250.
- This demonstrates recursive patterns, where different patterns are combined to perform a complex check on nested properties.
Summary of Assignments
- Easy: You learned to use the type pattern to check and assign a variable when an object is of a specific type.
- Medium: You used a switch expression to handle different values of a property compactly.
- Difficult: You explored recursive patterns to perform nested checks on both the type and properties of an object.
Top comments (0)