Skip to Content
Course content

236: List Patterns (C# 11)

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I was looking through some old code recently—a small utility for parsing coordinate-based movement in a grid system—and I realized how much I hated the way we used to handle arrays. We'd spend half the method just checking Length and then accessing indices, praying we didn't hit an IndexOutOfRangeException.

Fighting with array indices

Let's look at this snippet I found. It's meant to determine a movement direction based on an array of integers. It works, but it's a nightmare to read:

int[] move = { 1, 0, 5 };

string direction = "";
if (move.Length == 2 && move[0] == 1 && move[1] == 0) 
{
    direction = "North";
}
else if (move.Length == 2 && move[0] == 0 && move[1] == 1)
{
    direction = "East";
}
else if (move.Length > 2 && move[0] == 1 && move[move.Length - 1] == 5)
{
    direction = "Special Warp";
}

The logic is buried under the syntax. I'm spending more time thinking about move.Length - 1 than I am about the actual business logic. I remember when C# 11 introduced List Patterns, and I thought, "Finally, we can treat the array as a shape rather than a collection of slots."

Let's try a pattern match

What if I just describe what the array should look like? Instead of checking the length first, I can use the is operator with square brackets. Let's see if I can rewrite those first two checks.

if (move is [1, 0]) 
{
    direction = "North";
}
else if (move is [0, 1])
{
    direction = "East";
}

That's immediately cleaner. The [1, 0] pattern does two things at once: it ensures the array has exactly two elements and that those elements are exactly 1 and 0. No more Length == 2 boilerplate. It's almost like writing a regular expression, but for data structures.

Dealing with the noise

But real-world data is rarely that tidy. What if I only care that the first element is 1, but I don't care what the second element is? In the old way, I'd just omit the check for move[1]. With list patterns, I can use the discard symbol _.

// I only care that it starts with 1, and has exactly two elements
if (move is [1, _]) 
{
    Console.WriteLine("Moving North-ish...");
}

Wait, I just noticed something. If the array has three elements, [1, _] will return false because the pattern expects exactly two elements. If I want to say "starts with 1, and I don't care how many elements follow," I need something more flexible. That's where the slice pattern .. comes in.

Handling the gaps

The slice pattern .. is the real power move here. It represents zero or more elements. Let's go back to that "Special Warp" logic—where the array starts with 1 and ends with 5, regardless of what's in the middle.

// Starts with 1, ends with 5, anything (or nothing) in between
if (move is [1, .., 5]) 
{
    direction = "Special Warp";
}

I love this because it's declarative. I'm not calculating indices; I'm describing a sequence. I can even combine these. For instance, if I want to match any array that starts with 1, 2, and then has whatever else, I'd use [1, 2, ..]. If I want to match an empty array? Just [].

If we wrap this all into a switch expression, the transformation is pretty dramatic:

string direction = move switch
{
    [1, 0] => "North",
    [0, 1] => "East",
    [1, ..] => "Northward Trend",
    [.., 5] => "Ending in Warp",
    [1, .., 5] => "Special Warp", // Be careful: order matters!
    [] => "Stationary",
    _ => "Unknown"
};

One thing to watch out for: the order of patterns. Since the switch evaluates top-to-bottom, a more general pattern like [1, ..] would swallow a more specific one like [1, 0] if it came first. Always put your most specific "shapes" at the top.




📋 Practical Task

Implement a Packet Header Validator

You are building a network tool that inspects byte arrays (packets). You need to create a method called ValidatePacket(byte[] packet) that returns a string describing the packet type based on the following List Pattern rules:

  • An empty array [] should return "Empty Packet".
  • An array that is exactly [255, 0] should return "Keep-Alive".
  • An array that starts with 255, followed by any number of elements, and ends with 10 should return "Encrypted Data".
  • An array that starts with 1, followed by any number of elements should return "Standard Request".
  • Any other pattern should return "Malformed Packet".

Implement this logic using a switch expression and list patterns.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.