C# > Language Features by Version > C# 6 to C# 12 Highlights > Pattern Matching Enhancements (C# 8+)

Type and Property Pattern Matching with Enhanced 'when' Clauses

This example demonstrates enhanced pattern matching capabilities introduced in C# 8 and improved in later versions. It showcases type patterns, property patterns, and the use of 'when' clauses for more complex conditional logic within a single `switch` statement. We will also show how to use the `is` operator to check properties within an object, which is useful for cases where an object may be null or have properties that need to be validated before use.

Basic Example with Type and Property Patterns

This code defines a `Shape` class and two derived classes, `Circle` and `Rectangle`. The `DescribeShape` method uses a `switch` expression with pattern matching to determine the type of the shape and extract relevant properties like `Radius`, `Width`, and `Height`. The `when` clauses add further conditions to refine the matching logic. Notice how the `null` check is handled directly within the pattern matching construct. Also, note that `var r`, `var w`, and `var h` are used to capture the values of the matched properties directly.

using System;

public class Shape
{
    public double Area { get; set; }
}

public class Circle : Shape
{
    public double Radius { get; set; }
}

public class Rectangle : Shape
{
    public double Width { get; set; }
    public double Height { get; set; }
}

public static class PatternMatchingDemo
{
    public static string DescribeShape(Shape shape)
    {
        return shape switch
        {
            Circle { Radius: var r } when r > 0 => $"Circle with radius {r}",
            Rectangle { Width: var w, Height: var h } when w > 0 && h > 0 => $"Rectangle with width {w} and height {h}",
            Shape s when s.Area > 100 => "Large shape with area > 100",
            null => "No shape",
            _ => "Unknown shape"
        };
    }

    public static void Main(string[] args)
    {
        Circle circle = new Circle { Radius = 5, Area = 78.5 };
        Rectangle rectangle = new Rectangle { Width = 10, Height = 6, Area = 60 };
        Shape largeShape = new Shape { Area = 150 };

        Console.WriteLine(DescribeShape(circle));
        Console.WriteLine(DescribeShape(rectangle));
        Console.WriteLine(DescribeShape(largeShape));
        Console.WriteLine(DescribeShape(null));
    }
}

Concepts Behind the Snippet

Pattern matching allows you to write more concise and readable code for conditional logic based on the structure and properties of objects. It combines type checking, property access, and conditional branching into a single expression. The `switch` expression provides a declarative way to express complex logic that would otherwise require multiple `if-else` statements. The `when` clause allows for applying further filtering conditions to the pattern match.

Real-Life Use Case

Imagine processing different types of messages from a queue. Each message type has a different structure and requires specific handling. Pattern matching allows you to easily route messages to the appropriate handler based on their type and content. For instance, you might have `OrderMessage`, `PaymentMessage`, and `ShippingMessage` classes, and your processing logic could use pattern matching to dispatch each message to the correct handler method.

Best Practices

  • Order matters: The order of the patterns in the `switch` expression is significant. The first matching pattern will be executed. Therefore, more specific patterns should be placed before more general patterns.
  • Exhaustiveness: For switch statements with an enum or sealed class, the compiler can verify that your patterns are exhaustive and cover all possible cases. Use the `_` (discard pattern) to handle the default case (when none of the other patterns match).
  • Readability: Keep your patterns concise and easy to understand. Avoid overly complex `when` clauses. Consider refactoring complex logic into separate methods or functions.
  • Null checking: Explicitly handle null cases, preferably with `null` pattern.

Interview Tip

Be prepared to explain the benefits of pattern matching over traditional `if-else` statements. Highlight the improved readability, conciseness, and expressiveness. Also, be ready to discuss the different types of patterns available (type patterns, property patterns, positional patterns, etc.) and when to use each one.

When to Use Pattern Matching

Pattern matching is particularly useful when dealing with algebraic data types, discriminated unions, or any situation where you need to perform different actions based on the structure and properties of objects. It's also a good choice when you want to avoid nested `if-else` statements and create more readable and maintainable code.

Alternatives

The primary alternative to pattern matching is using a series of `if-else` statements with type checks (using the `is` operator) and property accessors. However, this approach can quickly become verbose and difficult to read, especially with complex conditions. Another option involves using the visitor pattern in Object Oriented programming, but that can add complexity.

Pros

  • Readability: Pattern matching often leads to more concise and readable code compared to traditional `if-else` statements.
  • Expressiveness: Pattern matching allows you to express complex conditional logic in a declarative way.
  • Type safety: The compiler can perform type checking and ensure that your patterns are valid.
  • Conciseness: Reduces boilerplate code by combining type checking, property access, and conditional branching into a single expression.

Cons

  • Complexity: Overly complex patterns can be difficult to understand and maintain.
  • Performance: In some cases, pattern matching might have a slight performance overhead compared to simple `if-else` statements, although the difference is usually negligible.

FAQ

  • What versions of C# support pattern matching?

    Pattern matching was introduced in C# 7.0 and has been significantly enhanced in subsequent versions, especially C# 8.0 and later. C# 9.0 introduced relational patterns, logical patterns, and more.
  • What is the discard pattern?

    The discard pattern (`_`) is a wildcard that matches any value. It's typically used in a `switch` statement to handle the default case when none of the other patterns match.
  • What are 'when' clauses in pattern matching?

    A 'when' clause adds an additional boolean condition to a pattern. The pattern will only match if both the pattern itself and the 'when' clause condition are true.