C# Programming Tutorial 0/45 lessons ~6 min read Lesson 11

    Pattern Matching

    Pattern matching unifies type tests, deconstruction, and conditional logic into one expressive feature set.

    Course progress0%
    Focus
    10 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    Introduction

    Pattern matching unifies type tests, deconstruction, and conditional logic into one expressive feature set. C# evolved from is checks to recursive patterns, list patterns (C# 11), and switch expressions that rival functional languages while staying imperative-friendly.

    Enterprise DTOs, API result types (Success/Failure discriminated unions), and JSON polymorphism all benefit from exhaustive pattern matching. Interviewers at Microsoft-heavy shops expect fluent use of property, positional, and relational patterns.

    This lesson ties together is, switch, list patterns, and negation patterns with real domain examples.

    The story

    A mobile banking app calls a backend API that returns either a success payload with a message or an error with an HTTP-style code. The client must display "OK: Transfer complete" for successes, a friendly "Not found" for 404 errors, and a generic retry prompt for server failures — without a chain of if (result is ApiSuccess) followed by nested type checks.

    Pattern matching on a sealed result hierarchy is how modern C# services handle discriminated unions cleanly.

    Understanding the topic

    Key concepts

    • Type pattern: obj is string s binds and casts.
    • Constant pattern: x is 42 or case 42:
    • Property pattern: p is { Age: >= 18, Country: "US" }
    • Positional pattern: Point(0, 0) for deconstruction match.
    • List pattern: [first, .. rest] matches collection shape.
    • not pattern negates: x is not null.

    Step-by-step explanation

    1. Compiler lowers patterns to type checks and comparisons.
    2. Switch arms evaluated sequentially — order matters.
    3. Deconstruction extracts record/tuple components in pattern.
    4. List patterns use Length and slice .. for rest.
    5. Exhaustiveness analysis on enums and bool.
    6. Combine with when for additional guards.

    Practical code example

    Discriminated API result handling with positional and property patterns:

    csharp
    namespace TechLearningPro.PatternMatching;
    public abstract record ApiResult;
    public sealed record ApiSuccess<T>(T Data) : ApiResult;
    public sealed record ApiError(int Code, string Message) : ApiResult;
    public static class ResultHandler
    {
    public static string Describe(ApiResult result) => result switch
    {
    ApiSuccess<string> { Data: var msg } =>
    quot;OK: {msg}",
    ApiError { Code: 404, Message: var m } =>
    quot;Not found — {m}",
    ApiError { Code: >= 500 } => "Server error — retry later.",
    ApiError e =>
    quot;Error {e.Code}: {e.Message}",
    _ => "Unknown result"
    };
    }

    Line-by-line code explanation

    • public abstract record ApiResult is the base type for all API outcomes.
    • ApiSuccess<T>(T Data) represents a successful response carrying typed data.
    • ApiError(int Code, string Message) represents a failure with code and message.
    • Describe(ApiResult result) => result switch pattern-matches on the result type.
    • ApiSuccess<string> { Data: var msg } extracts the message into a variable in the same arm.
    • => $"OK: {msg}" formats the success string for display.
    • ApiError { Code: 404, Message: var m } matches not-found errors specifically.
    • ApiError { Code: >= 500 } uses a relational pattern for any server error code.
    • ApiError e => ... is the general error arm when no earlier arm matched.
    • _ => "Unknown result" handles unexpected types defensively.

    Key takeaway: Sealed hierarchy enables exhaustive matching. Property patterns nest inside positional records. Relational >= on Code property.

    Real-world use

    Where you'll use this in production

    • Parsing heterogeneous JSON nodes by shape.
    • Validation results with Success/Failure types.
    • AST traversal in compilers and rule engines.
    • Game entity interaction by type and state.

    Best practices

    • Use sealed class hierarchies for domain variants.
    • Prefer records for positional patterns.
    • Order switch arms from specific to general.
    • Avoid pattern matching on mutable classes — race conditions.
    • Unit test each pattern arm independently.

    Common mistakes

    • Wrong arm order — general pattern catches before specific.
    • Matching mutable object state that changes mid-switch.
    • Overly complex nested patterns — extract helpers.
    • Ignoring compiler exhaustiveness warnings.

    Advanced interview questions

    Q1BeginnerType pattern vs cast?
    is Type t combines check and safe bind; cast throws on failure.
    Q2BeginnerProperty pattern syntax?
    obj is { Prop: value, Other: > 0 }
    Q3IntermediateList pattern [..] meaning?
    Matches any length; .. captures rest slice; [a,b] fixed length.
    Q4IntermediateImplement Result without exceptions?
    Record Success/Failure; Map/Bind methods; Match for consumption.
    Q5AdvancedParse JSON polymorphic array of shapes with pattern matching.
    Deserialize to JsonElement; switch on property presence; bind typed records; fail on unknown discriminator.

    Summary

    Pattern matching combines type tests, deconstruction, and conditions. Switch expressions with property/relational patterns replace verbose if chains. Sealed record hierarchies enable safe exhaustive handling. List patterns (C# 11) match collection structure. Next: methods and reusable logic blocks.

    Ready to mark this lesson complete?Track your journey across the entire course.