Introduction
C# has evolved significantly over the years, and one of the most impactful changes came with the introduction of pattern matching in C# 6. This feature, coupled with the enhanced switch
expression in later versions, has dramatically improved the syntax, readability, and expressiveness of case analysis in C# code.
In this post, we'll delve deep into the world of pattern matching, exploring how it works, its various forms, and how it can be effectively used to write cleaner, more maintainable code.
The is
Operator: A First Step
Before we dive into switch
statements, let's understand the is
operator, which laid the groundwork for pattern matching.
The is
operator checks if an expression is compatible with a given type. If it is, it can optionally declare a variable of that type.
object shape = new Circle();
if (shape is Circle circle)
{
// We can use 'circle' here, which is of type Circle
Console.WriteLine($"Circle radius: {circle.Radius}");
}
This code checks if shape
is a Circle
. If so, it declares a new variable circle
of type Circle
and assigns the converted value to it.
The switch
Statement: A New Era
C# 7 introduced significant improvements to the switch
statement, allowing it to work with any type, not just integral types and strings. This, combined with pattern matching, opened up new possibilities.
object shape = new Rectangle();
switch (shape)
{
case Circle c:
Console.WriteLine($"Circle radius: {c.Radius}");
break;
case Rectangle r:
Console.WriteLine($"Rectangle width: {r.Width}, height: {r.Height}");
break;
default:
Console.WriteLine("Unknown shape");
break;
}
In this example, the switch
statement efficiently determines the type of the shape
object and executes the appropriate code block.
Pattern Matching with the switch
Expression
C# 8 introduced the switch
expression, providing a more concise and functional-style syntax for pattern matching.
object shape = new Circle();
string result = shape switch
{
Circle c => $"Circle with radius {c.Radius}",
Rectangle r => $"Rectangle with width {r.Width} and height {r.Height}",
_ => "Unknown shape"
};
The switch
expression evaluates to a value based on the matched pattern.
Types of Patterns
C# supports various patterns, including:
- Constant patterns: Match literal values.
- Type patterns: Match types.
- Var patterns: Match any type.
- Discard patterns: Match any value and discard it.
- Property patterns: Match property values.
- Tuple patterns: Match tuple elements.
- Positional patterns: Match elements by position.
-
Logical patterns: Combine patterns using
and
,or
, andnot
.
Example: Using Different Patterns
int age = 25;
string message = age switch
{
< 18 => "Minor",
>= 18 and < 65 => "Adult",
_ => "Senior"
};
This example demonstrates the use of constant, relational, and logical patterns.
Deconstruction and Pattern Matching
C# supports deconstruction, which can be combined with pattern matching to extract values from objects.
(int x, int y) point = (3, 4);
int result = point switch
{
(0, 0) => 0,
(var x, var y) when x == y => x * 2,
(var x, var y) => x + y
};
Additional Features
-
Guard clauses: Use
when
to add conditions to patterns. - Multiple case labels: Combine multiple patterns with the same result.
-
Default case: Handle unmatched cases with the
_
pattern.
Conclusion
Pattern matching is a powerful feature that significantly enhances C# code readability and maintainability. By understanding the different types of patterns and how to use them effectively, you can write cleaner, more expressive, and less error-prone code.
Remember: The key to mastering pattern matching is practice. Experiment with different patterns and scenarios to fully grasp its potential.
Would you like to explore specific use cases or dive deeper into any particular aspect of pattern matching?
Top comments (0)