0Pricing
C# Academy · Lesson

Type and Property Patterns

Match on shape and members.

Type and Property Patterns is a free C# Academy lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the C# Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

The Type Pattern

A type pattern matches when the input is an instance of a given type. Written as just the type name in a switch arm, it tests the runtime type of the value.

This replaces clumsy is plus cast chains with a clean, single-expression check that also narrows the type for you.

object o = "hello";
string kind = o switch
{
    int    => "number",
    string => "text",
    _      => "unknown"
};
System.Console.WriteLine(kind);

Declaration Patterns

A declaration pattern adds a variable name after the type: string s. When it matches, the input is cast and bound to that variable.

You can then use the typed variable on the right side of the arm, accessing members specific to that type without an extra cast.

object o = "coddy";
string result = o switch
{
    string s => $"len {s.Length}",
    int n    => $"value {n}",
    _        => "other"
};
System.Console.WriteLine(result);

A Runnable Type Switch

Type patterns are perfect for processing heterogeneous data, such as parsing values from an object array.

Here each element is matched by its runtime type and formatted accordingly. The bound variable carries the narrowed type into the result expression.

using System;

class Program {
    static string Describe(object o) => o switch {
        int i    => $"int {i}",
        double d => $"double {d}",
        string s => $"string '{s}'",
        _        => "?"
    };
    static void Main() {
        foreach (var x in new object[]{ 1, 2.5, "hi" })
            Console.WriteLine(Describe(x));
    }
}

The Property Pattern

A property pattern inspects an object's members using braces: { Property: value }. It matches when each listed property satisfies its nested pattern.

This lets you branch on the shape and contents of an object directly, without writing manual member comparisons.

record Person(string Name, int Age);

var p = new Person("Ada", 30);
string stage = p switch
{
    { Age: < 18 } => "minor",
    { Age: >= 65 } => "senior",
    _ => "adult"
};
System.Console.WriteLine(stage);

Multiple Properties

You can test several properties at once by separating them with commas inside the braces. All must match for the arm to fire.

This expresses compound conditions clearly, such as matching an object that has both a status and a specific count.

record Order(string Status, int Items);

var order = new Order("Open", 0);
string msg = order switch
{
    { Status: "Open", Items: 0 } => "empty cart",
    { Status: "Open" } => "shopping",
    _ => "closed"
};
System.Console.WriteLine(msg);

Combining Type and Property

Type and property patterns combine: write the type, then a brace block of properties. The arm matches only if the input is that type and its properties match.

This is powerful when handling base-class references or interfaces where you want to branch on concrete subtypes and their state together.

using System;

abstract record Shape;
record Circle(double R) : Shape;
record Square(double Side) : Shape;

class Program {
    static string Big(Shape s) => s switch {
        Circle { R: > 10 } => "big circle",
        Square { Side: > 10 } => "big square",
        _ => "small"
    };
    static void Main() => Console.WriteLine(Big(new Circle(12)));
}

Nested Property Patterns

Property patterns can nest. If a property is itself an object, you match its members with another brace block.

For example { Address: { City: "Paris" } } drills two levels deep in one expression. This avoids null-prone chains of dotted access.

record Address(string City);
record User(string Name, Address Address);

var u = new User("Lin", new Address("Paris"));
bool inParis = u is { Address: { City: "Paris" } };
System.Console.WriteLine(inParis);

Extended Property Access

Since C# 10 you can use a dotted path inside a property pattern: { Address.City: "Paris" }. It is shorthand for nested braces.

This flattens deep checks into one readable line while still safely handling intermediate values during the match.

record Address(string City);
record User(string Name, Address Address);

var u = new User("Lin", new Address("Rome"));
string where = u switch
{
    { Address.City: "Paris" } => "FR",
    { Address.City: "Rome" }  => "IT",
    _ => "??"
};
System.Console.WriteLine(where);

Capturing the Whole Match

You can bind a variable to the entire matched value by adding a name after the property pattern: { Age: > 18 } adult.

The variable adult then refers to the full object inside the arm, letting you use it after confirming its properties pass the test.

using System;

record Person(string Name, int Age);

class Program {
    static void Main() {
        Person p = new("Sam", 40);
        string r = p switch {
            { Age: >= 18 } adult => $"{adult.Name} can vote",
            _ => "too young"
        };
        Console.WriteLine(r);
    }
}

Null Handling

A property pattern never matches null, because there is no object to inspect. So { } (empty braces) matches any non-null value.

This makes not null and { } useful idioms for null checks, and ensures property-based arms safely skip null inputs.

string? maybe = null;
string state = maybe switch
{
    null => "missing",
    { Length: 0 } => "empty",
    _ => "has text"
};
System.Console.WriteLine(state);

Why Patterns Beat Casts

Before patterns, type-based branching meant is checks followed by casts and null guards. Type and property patterns fold all of that into one expression.

The compiler also tracks exhaustiveness and reachability, catching dead arms and missing cases that hand-written if-chains would silently ignore.

object o = 42;
// Old: if (o is int) { int n = (int)o; ... }
// New:
if (o is int n) System.Console.WriteLine(n + 1);

Quick Check

Test your understanding of type and property patterns.

Recap

Type patterns match by runtime type and can bind a typed variable. Property patterns inspect members with { Prop: pattern }, support nesting and dotted paths, and never match null.

Combine them to branch on both shape and state. Next we cover relational and logical patterns.

Frequently asked questions

Is the “Type and Property Patterns” lesson free?

Yes — the full text of “Type and Property Patterns” is free to read here on the web, and the C# Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the C# Academy course, upgrade to CoddyKit PRO.

What will I learn in “Type and Property Patterns”?

Match on shape and members. You practise C# Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start C# Academy?

No prior experience is required. C# Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Type and Property Patterns” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this C# Academy lesson?

Yes. Every C# Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. switch Expressions
  2. Type and Property Patterns
  3. Relational and Logical Patterns
  4. List and Tuple Patterns
← Back to C# Academy