Pattern matching in C# — is, switch, property, and list patterns

Pattern matching lets you inspect and destructure values in a single expression. It started in C# 7 with is type patterns and has expanded across every subsequent version — C# 8 added property patterns, C# 9 added relational and logical patterns, C# 10 added extended property patterns, and C# 11 added list patterns.

All patterns work in both is expressions and switch expressions.

Type Pattern — is T variable

Test the type of an object and bind it to a new variable in one step. No cast required.

C# Example Code
object shape = new Circle(5.0);

// Old way (C# 5 and earlier)
if (shape is Circle)
{
    Circle c = (Circle)shape; // separate cast
    Console.WriteLine(c.Radius);
}

// Type pattern (C# 7+) — test and bind in one expression
if (shape is Circle circle)
    Console.WriteLine($"Circle with radius {circle.Radius}"); // Circle with radius 5

// Works with interfaces
object value = "hello";
if (value is IEnumerable<char> chars)
    Console.WriteLine(chars.Count()); // 5

record Circle(double Radius);

Constant Pattern

Match a specific value — useful for null checks and literal comparisons.

C# Example Code
object? obj = null;

// Null check with constant pattern
if (obj is null)
    Console.WriteLine("obj is null"); // obj is null

if (obj is not null)
    Console.WriteLine("has value");

// Constant values
int code = 404;
string message = code switch
{
    200 => "OK",
    404 => "Not Found",
    500 => "Internal Server Error",
    _   => "Unknown"
};
Console.WriteLine(message); // Not Found

Relational Pattern (C# 9+)

Compare values without writing the variable name twice.

C# Example Code
int score = 78;

string grade = score switch
{
    >= 90 => "A",
    >= 80 => "B",
    >= 70 => "C",
    >= 60 => "D",
    _     => "F"
};
Console.WriteLine(grade); // C

// Works with is too
double temperature = -5.0;
if (temperature is < 0)
    Console.WriteLine("Freezing");      // Freezing
if (temperature is >= 0 and < 25)
    Console.WriteLine("Comfortable");

Logical Patterns — and, or, not (C# 9+)

Combine patterns with and, or, and not keywords.

C# Example Code
// not — negate a pattern
object? val = "hello";
if (val is not null)
    Console.WriteLine("not null"); // not null

// and — both patterns must match
int age = 25;
if (age is >= 18 and <= 65)
    Console.WriteLine("Working age"); // Working age

// or — either pattern matches
char ch = 'e';
if (ch is 'a' or 'e' or 'i' or 'o' or 'u')
    Console.WriteLine("Vowel"); // Vowel

// Combine in a switch expression
double speed = 95.0;
string zone = speed switch
{
    < 0         => "Invalid",
    0           => "Stopped",
    > 0 and < 30  => "Slow",
    >= 30 and < 70 => "Normal",
    >= 70 and < 120 => "Fast",
    _           => "Dangerous"
};
Console.WriteLine(zone); // Fast

Property Pattern

Inspect properties without extracting them to variables first.

C# Example Code
public record Order(string Status, decimal Total, string Country);

var order = new Order("Shipped", 250.00m, "US");

// Match on one property
if (order is { Status: "Shipped" })
    Console.WriteLine("Your order is on its way!");

// Match on multiple properties
string message = order switch
{
    { Status: "Cancelled" }                                   => "Order cancelled.",
    { Status: "Shipped", Country: "US" }                      => "US shipment in progress.",
    { Status: "Shipped", Total: >= 200, Country: not "US" }   => "International express.",
    { Status: "Pending" }                                     => "Awaiting processing.",
    _                                                         => "Check order status."
};
Console.WriteLine(message); // US shipment in progress.

Extended Property Pattern (C# 10+)

Navigate nested properties using . inside the pattern.

C# Example Code
public record Address(string City, string Country);
public record Customer(string Name, Address Address);

var customer = new Customer("Alice", new Address("London", "UK"));

// C# 9: nested property pattern
if (customer is { Address: { Country: "UK" } })
    Console.WriteLine("UK customer");

// C# 10: extended — same but shorter
if (customer is { Address.Country: "UK" })
    Console.WriteLine("UK customer"); // UK customer

Positional Pattern

Deconstructs an object and matches on the deconstructed values. Works with records and types that have a Deconstruct method.

C# Example Code
public record Point(int X, int Y);

var point = new Point(3, -1);

string quadrant = point switch
{
    ( > 0,  > 0) => "Q1 (+,+)",
    ( < 0,  > 0) => "Q2 (-,+)",
    ( < 0,  < 0) => "Q3 (-,-)",
    ( > 0,  < 0) => "Q4 (+,-)",
    (0, 0)        => "Origin",
    _             => "On an axis"
};
Console.WriteLine(quadrant); // Q4 (+,-)

List Pattern (C# 11+)

Match the structure and contents of a list or array.

C# Example Code
int[] numbers = { 1, 2, 3, 4, 5 };

// Starts with 1 and 2
if (numbers is [1, 2, ..])
    Console.WriteLine("Starts with 1, 2"); // Starts with 1, 2

// Exactly three elements, middle is anything
if (numbers is [_, _, _, _, _])
    Console.WriteLine("Has exactly 5 elements"); // Has exactly 5 elements

// Ends with 4 and 5
if (numbers is [.., 4, 5])
    Console.WriteLine("Ends with 4, 5"); // Ends with 4, 5

// Practical: parse a command split on spaces
string[] args = { "move", "left", "10" };
string result = args switch
{
    ["move", var direction, var amount] => $"Moving {direction} by {amount}",
    ["stop"]                            => "Stopping",
    []                                  => "No command",
    _                                   => "Unknown command"
};
Console.WriteLine(result); // Moving left by 10

Real-World Example: Discount Calculator

C# Example Code
public record Cart(string CustomerType, decimal Total, bool HasCoupon);

static decimal GetDiscount(Cart cart) => cart switch
{
    { CustomerType: "VIP",   Total: >= 500 }        => 0.20m,  // 20% — VIP high spender
    { CustomerType: "VIP" }                          => 0.15m,  // 15% — VIP standard
    { CustomerType: "Member", HasCoupon: true }      => 0.12m,  // 12% — member + coupon
    { CustomerType: "Member" }                       => 0.08m,  // 8%  — member
    { HasCoupon: true, Total: >= 100 }               => 0.05m,  // 5%  — guest + coupon + min spend
    _                                                => 0.00m   // no discount
};

var cart1 = new Cart("VIP",    750m, false);
var cart2 = new Cart("Member", 200m, true);
var cart3 = new Cart("Guest",  150m, true);

Console.WriteLine($"VIP:    {GetDiscount(cart1):P0}"); // 20%
Console.WriteLine($"Member: {GetDiscount(cart2):P0}"); // 12%
Console.WriteLine($"Guest:  {GetDiscount(cart3):P0}"); // 5%