C# tutorials > Modern C# Features > C# 6.0 and Later > What are list patterns in C# 11 and how can you use them for matching?

What are list patterns in C# 11 and how can you use them for matching?

C# 11 introduced list patterns, a powerful feature for matching sequences of elements within collections like arrays and lists. List patterns allow you to examine the structure and content of these collections in a concise and expressive way using pattern matching syntax. This tutorial will explore how to use list patterns effectively for various matching scenarios.

Basic List Pattern Matching

This example demonstrates the simplest form of list pattern matching. The is operator, combined with the list pattern syntax [element1, element2, ...], checks if the array numbers matches the specified sequence. The _ (discard pattern) is used to match any element at that position.

using System;

public class ListPatternExample
{
    public static void Main(string[] args)
    {
        int[] numbers = { 1, 2, 3, 4, 5 };

        if (numbers is [1, 2, 3, 4, 5])
        {
            Console.WriteLine("The array is exactly {1, 2, 3, 4, 5}");
        }

        if (numbers is [1, _, 3, _, 5])
        {
            Console.WriteLine("The array starts with 1, then any value, then 3, then any value, and ends with 5.");
        }
    }
}

Using Slice Patterns

Slice patterns (..) allow you to match a sequence of zero or more elements. In the first if statement, we check if the array starts with 1, 2 and ends with 5, irrespective of the number of elements in between. The second if statement checks if the array ends with 4, 5. Slice patterns are very powerful for checking prefixes, suffixes, and contents regardless of overall length.

using System;

public class ListPatternExample
{
    public static void Main(string[] args)
    {
        int[] numbers = { 1, 2, 3, 4, 5 };

        if (numbers is [1, 2, .., 5])
        {
            Console.WriteLine("The array starts with 1, 2 and ends with 5, with any number of elements in between.");
        }

        if (numbers is [.., 4, 5])
        {
            Console.WriteLine("The array ends with 4, 5, with any number of elements before them.");
        }
    }
}

Capturing Elements with List Patterns

You can capture elements matched by list patterns into variables using the var keyword (or explicitly typed variables). This allows you to access the matched values within the if block. Here, we capture the first, third and last elements of the array into first, third, and last respectively.

using System;

public class ListPatternExample
{
    public static void Main(string[] args)
    {
        int[] numbers = { 1, 2, 3, 4, 5 };

        if (numbers is [var first, _, var third, .., var last])
        {
            Console.WriteLine($"First: {first}, Third: {third}, Last: {last}");
        }
    }
}

Type Patterns within List Patterns

List patterns support type patterns, allowing you to match elements based on their type. In this example, we are checking if the mixed array contains an int, a string, a double, another int, and finally another string. If the types match, the values are captured in the variables a, b, c, and d.

using System;

public class ListPatternExample
{
    public static void Main(string[] args)
    {
        object[] mixed = { 1, "hello", 3.14, 4, "world" };

        if (mixed is [int a, string b, double _, int c, string d])
        {
            Console.WriteLine($"a: {a}, b: {b}, c: {c}, d: {d}");
        }
    }
}

Nested List Patterns

List patterns can be nested to match multi-dimensional arrays or lists of lists. This example checks if the matrix array is exactly [[1, 2], [3, 4]].

using System;

public class ListPatternExample
{
    public static void Main(string[] args)
    {
        int[][] matrix = { new int[] { 1, 2 }, new int[] { 3, 4 } };

        if (matrix is [[1, 2], [3, 4]])
        {
            Console.WriteLine("The matrix is exactly [[1, 2], [3, 4]]");
        }
    }
}

Concepts Behind the Snippet

The core concept behind list patterns is structural matching. It allows you to deconstruct a sequence and match its elements against a predefined pattern. The is operator combined with list patterns provides a declarative and readable way to express complex matching logic. Key components include:

  • Element Patterns: Matching individual elements using constants, variables, or discard patterns.
  • Slice Patterns: Matching subsequences of any length.
  • Type Patterns: Matching elements based on their type.

Real-Life Use Case

List patterns are useful in scenarios such as:

  • Data validation: Ensuring that an input array conforms to a specific format.
  • Protocol parsing: Decoding network packets based on their structure.
  • Game development: Matching player input sequences to trigger actions.
  • Compiler design: Pattern matching on abstract syntax trees (ASTs).

Imagine you're parsing a command line input. You could use a list pattern to ensure the input starts with a specific command and then extract the required arguments.

Best Practices

When using list patterns:

  • Keep patterns concise: Avoid overly complex patterns that are difficult to read and understand.
  • Use descriptive variable names: When capturing elements, use meaningful variable names to improve code clarity.
  • Consider performance: While list patterns are generally efficient, avoid using them in performance-critical sections if simpler alternatives are available.
  • Handle edge cases: Make sure your patterns cover all possible input variations, including empty lists or unexpected data.

Interview Tip

When discussing list patterns in an interview, be prepared to explain:

  • The different types of patterns (element, slice, type).
  • How to capture elements using var.
  • Use cases where list patterns are particularly beneficial.
  • Potential performance considerations.

Demonstrate your understanding by providing clear and concise examples.

When to use them

Use list patterns when you need to:

  • Match the structure and content of a sequence.
  • Extract specific elements from a sequence based on their position or type.
  • Simplify complex conditional logic involving sequence manipulation.

However, if you only need to check for the existence of a single element or a simple condition, other methods like Any() or Contains() might be more appropriate.

Memory footprint

List patterns themselves don't directly allocate significant memory. The primary memory usage comes from the underlying data structures you are matching against (arrays, lists, etc.). However, be mindful of memory implications when capturing elements into new variables. Each captured element will require memory to store its value. If you're dealing with very large collections, consider whether you truly need to capture all matched elements or if you can process them directly within the matching block.

Alternatives

Alternatives to list patterns include:

  • Traditional looping and indexing: Using for or foreach loops with index-based access to elements.
  • LINQ queries: Using LINQ methods like Where(), Select(), First(), etc., to filter and transform sequences.
  • Regular expressions: For matching patterns within strings (less suitable for general list matching).

List patterns often provide a more concise and readable solution compared to these alternatives, especially for complex matching scenarios. The best choice depends on the specific requirements and complexity of the task.

Pros

Advantages of using list patterns:

  • Readability: Provide a declarative and expressive way to express complex matching logic.
  • Conciseness: Reduce the amount of code required compared to traditional looping or LINQ queries.
  • Type safety: Support type patterns, allowing you to match elements based on their type.
  • Structural matching: Allow you to match the structure and content of a sequence simultaneously.

Cons

Disadvantages of using list patterns:

  • Learning curve: Require understanding of the pattern matching syntax.
  • Potential performance overhead: May introduce a slight performance overhead compared to simpler alternatives in some cases.
  • Limited applicability: Not suitable for all sequence manipulation tasks.

FAQ