C# tutorials > Modern C# Features > C# 6.0 and Later > What are recursive patterns in C# 8.0?

What are recursive patterns in C# 8.0?

Recursive patterns in C# 8.0 are a powerful feature that extends pattern matching capabilities by allowing you to match properties of nested objects and deconstruct objects in a recursive manner. They are particularly useful for working with complex data structures where you need to examine multiple levels of an object hierarchy.

Basic Recursive Pattern Example

This example demonstrates a simple recursive pattern. We have a Person class with a nested Address class. The GetCity method uses pattern matching to extract the city from the HomeAddress. The first case { HomeAddress: { City: "London" } } checks if the city is London. The second case { HomeAddress: { City: var city } } extracts the city into a variable named city.

public class Address
{
    public string Street { get; set; }
    public string City { get; set; }
}

public class Person
{
    public string FirstName { get; set; }
    public Address HomeAddress { get; set; }
}

public static class PatternMatchingExample
{
    public static string GetCity(Person person)
    {
        return person switch
        {
            { HomeAddress: { City: "London" } } => "Person lives in London",
            { HomeAddress: { City: var city } } => $"Person lives in {city}",
            _ => "Unknown location"
        };
    }
}

Concepts Behind the Snippet

The key concepts at play here are:

  • Nested Properties: Recursive patterns allow you to access properties of properties (e.g., person.HomeAddress.City) directly within the pattern.
  • Property Patterns: { PropertyName: Pattern } is the syntax for matching a property against a pattern.
  • Variable Declaration: The var keyword can be used to capture the value of a property.
  • Discard Pattern: You can use the discard pattern (_) to ignore a property if you don't need its value.

Real-Life Use Case: Configuration Validation

Consider a configuration object with nested security settings. Recursive patterns can be used to validate that all required fields are present. This example validates that the ConnectionString, Username, and Password properties are not null. The not null pattern checks if a value is not null.

public class DatabaseConfig
{
    public string ConnectionString { get; set; }
    public SecurityConfig Security { get; set; }
}

public class SecurityConfig
{
    public string Username { get; set; }
    public string Password { get; set; }
}

public static class ConfigValidator
{
    public static bool IsValid(DatabaseConfig config)
    {
        return config switch
        {
            { ConnectionString: not null, Security: { Username: not null, Password: not null } } => true,
            _ => false
        };
    }
}

Deconstruction with Recursive Patterns

Recursive patterns can also be combined with deconstruction. The Point class defines a Deconstruct method. The ProcessPoint method then uses recursive patterns with deconstruction to match different point coordinates. The pattern (0, 0) matches the origin. The pattern (var x, 0) matches any point on the X-axis.

public class Point
{
    public int X { get; set; }
    public int Y { get; set; }
    public void Deconstruct(out int x, out int y)
    {
        x = X;
        y = Y;
    }
}

public static class PointProcessor
{
    public static string ProcessPoint(Point point)
    {
        return point switch
        {
            (0, 0) => "Origin",
            (var x, 0) => $"X-axis: {x}",
            (0, var y) => $"Y-axis: {y}",
            (var x, var y) => $"({x}, {y})"
        };
    }
}

Best Practices

Here are some best practices for using recursive patterns:

  • Keep Patterns Concise: Complex nested patterns can become difficult to read. Break down complex logic into smaller, more manageable patterns.
  • Handle Nulls Gracefully: Be mindful of null values in nested properties. Use nullable types or null checks to prevent null reference exceptions.
  • Prioritize Readability: Choose pattern names that are descriptive and easy to understand.

Interview Tip

When discussing recursive patterns in an interview, be prepared to explain how they improve code readability and maintainability, especially when dealing with complex object hierarchies. Also, be prepared to discuss null handling strategies when using recursive patterns.

When to Use Them

Use recursive patterns when:

  • You need to match properties of nested objects.
  • You want to deconstruct objects and match on their properties simultaneously.
  • You want to improve the readability of complex conditional logic.

Memory Footprint

Recursive patterns themselves don't significantly impact memory footprint. However, if you are creating new objects within the pattern (e.g., capturing values into new variables), that will contribute to memory allocation. Be mindful of creating unnecessary objects within your patterns.

Alternatives

Alternatives to recursive patterns include:

  • Traditional if statements: These can be used, but they often lead to more verbose and less readable code.
  • Chained null-conditional operators (?.) and null-coalescing operators (??): These can help with null handling but don't provide the same level of pattern matching.

Pros

  • Improved Readability: Recursive patterns make complex conditional logic easier to understand.
  • Conciseness: They can often replace multiple nested if statements with a single pattern.
  • Expressiveness: They allow you to express complex matching conditions in a declarative way.

Cons

  • Complexity: Overly complex patterns can become difficult to read and maintain.
  • Potential for Null Reference Exceptions: Careful null handling is required to avoid exceptions.
  • Learning Curve: Developers unfamiliar with pattern matching may need time to learn the syntax.

FAQ

  • What happens if a property in a recursive pattern is null?

    If a property in a recursive pattern is null and you don't handle it, you will likely get a NullReferenceException. Use nullable types or explicitly check for null values within your pattern or before using the pattern matching.

  • Can I use recursive patterns with custom classes and structs?

    Yes, you can use recursive patterns with any class or struct, as long as the properties you are matching are accessible.

  • Are there performance considerations when using recursive patterns?

    In most cases, the performance difference between recursive patterns and traditional if statements is negligible. However, very complex patterns might have a slight performance impact. Profile your code if performance is critical.