Paweł Łukasiewicz: programista blogger
Paweł Łukasiewicz
2026-04-01
Paweł Łukasiewicz: programista blogger
Paweł Łukasiewicz
2026-04-01
Udostępnij Udostępnij Kontakt
Wprowadzenie

Pattern Matching to jedna z najważniejszych ewolucji C# ostatnich lat! W 2015 roku miałeś if/else i switch. W 2026 roku masz potężny system rozpoznawania wzorców który eliminuje 90% boilerplate kodu!

W tym wpisie poznasz type patterns, property patterns, positional patterns, list patterns (C# 11), logical patterns (and/or/not), relational patterns (<, >, <=, >=), i switch expressions - najbardziej zwięzły kod jaki kiedykolwiek napisałeś!

📅 Timeline - ewolucja pattern matching w C#
  • C# 1.0-6.0 (do 2015) - Tylko if/else i switch statement
  • C# 7.0 (2017) - 🔥 Type patterns, constant patterns, var patterns
  • C# 8.0 (2019) - 🔥 Switch expressions, property patterns, positional patterns
  • C# 9.0 (2020) - 🔥 Relational patterns (<, >), logical patterns (and, or, not)
  • C# 10 (2021) - Extended property patterns
  • C# 11 (2022) - 🔥 List patterns [.., var x]
Type Patterns - rozpoznawanie typów (C# 7+)

Problem przed pattern matching

// Przed C# 7 - verbose type checking
object obj = GetObject();

if (obj is string)
{
    string str = (string)obj;  // Casting!
    Console.WriteLine($"String: {str.Length}");
}
else if (obj is int)
{
    int number = (int)obj;  // Casting!
    Console.WriteLine($"Int: {number * 2}");
}

// Type check + casting = repetition! 😱

Type Patterns - C# 7+

🎉 C# 7 - Type Patterns

is Type variable - sprawdza typ i przypisuje do zmiennej w jednym kroku!

// C# 7+ - type pattern z declaration
object obj = GetObject();

if (obj is string str)
{
    Console.WriteLine($"String: {str.Length}");  // str jest dostępny!
}
else if (obj is int number)
{
    Console.WriteLine($"Int: {number * 2}");
}
else if (obj is List list)
{
    Console.WriteLine($"List with {list.Count} items");
}

// Type check + assignment w jednej linii! ✨

Type patterns w switch

// Type patterns w switch statement
object value = GetValue();

switch (value)
{
    case string str:
        Console.WriteLine($"String of length {str.Length}");
        break;
    
    case int number when number > 0:
        Console.WriteLine($"Positive int: {number}");
        break;
    
    case int number:
        Console.WriteLine($"Non-positive int: {number}");
        break;
    
    case List list:
        Console.WriteLine($"List with {list.Count} items");
        break;
    
    case null:
        Console.WriteLine("Null value");
        break;
    
    default:
        Console.WriteLine("Unknown type");
        break;
}
❌ Przed C# 7 - verbose
object shape = GetShape();

if (shape is Circle)
{
    Circle c = (Circle)shape;
    Console.WriteLine(c.Radius);
}
else if (shape is Rectangle)
{
    Rectangle r = (Rectangle)shape;
    Console.WriteLine(r.Width);
}

// Type check + cast - dwa kroki!
✅ C# 7+ - zwięźle
object shape = GetShape();

if (shape is Circle c)
{
    Console.WriteLine(c.Radius);
}
else if (shape is Rectangle r)
{
    Console.WriteLine(r.Width);
}

// Jeden krok! ✨
Property Patterns - sprawdzanie properties (C# 8+)

Podstawowe property patterns

🎉 C# 8 - Property Patterns

{ Property: value } - sprawdzaj wartości properties w pattern!

// Property patterns w if
public record Person(string Name, int Age, string City);

Person person = GetPerson();

// Property pattern - sprawdź wartość property
if (person is { Age: 30 })
{
    Console.WriteLine("Person is 30 years old");
}

// Multiple properties
if (person is { Age: 30, City: "Warsaw" })
{
    Console.WriteLine("30 year old from Warsaw");
}

// Property + variable
if (person is { Age: var age, City: "Warsaw" })
{
    Console.WriteLine($"Age {age} from Warsaw");
}

Property patterns w switch expression

// Switch expression z property patterns
public record Order(int Id, string Status, decimal Total, bool IsPaid);

string GetOrderMessage(Order order) => order switch
{
    { Status: "Pending", IsPaid: false } => "Awaiting payment",
    { Status: "Pending", IsPaid: true } => "Payment received, processing...",
    { Status: "Shipped", IsPaid: false } => "Shipped but not paid - contact customer",
    { Status: "Shipped", IsPaid: true } => "Order shipped",
    { Status: "Delivered" } => "Order delivered",
    { Status: "Cancelled" } => "Order cancelled",
    _ => "Unknown status"
};

var order = new Order(1, "Pending", 100m, false);
Console.WriteLine(GetOrderMessage(order));  // "Awaiting payment"

Nested property patterns

// Nested property patterns - zagnieżdżone obiekty
public record Address(string City, string Country);
public record Customer(string Name, Address Address, bool IsPremium);
public record Order(Customer Customer, decimal Total);

string GetShippingInfo(Order order) => order switch
{
    // Nested property access
    { Customer.Address.Country: "Poland", Total: < 100 } 
        => "Domestic shipping: 15 PLN",
    
    { Customer.Address.Country: "Poland", Total: >= 100 } 
        => "Domestic shipping: FREE",
    
    { Customer: { IsPremium: true } } 
        => "Premium shipping: FREE",
    
    { Customer.Address.Country: "USA" } 
        => "International shipping: 50 USD",
    
    _ => "Contact for shipping quote"
};

var order = new Order(
    new Customer("Jan", new Address("Warsaw", "Poland"), false),
    150m
);

Console.WriteLine(GetShippingInfo(order));  // "Domestic shipping: FREE"

Extended property patterns (C# 10)

// C# 10 - extended property patterns (shorter syntax)
public record Point(int X, int Y);

// Przed C# 10
bool IsOrigin1(Point p) => p is { X: 0, Y: 0 };

// C# 10 - można pominąć { }
bool IsOrigin2(Point p) => p is { X: 0 and Y: 0 };  // and operator

// C# 10 - jeszcze krótsza składnia
string GetQuadrant(Point p) => p switch
{
    { X: > 0, Y: > 0 } => "Quadrant I",
    { X: < 0, Y: > 0 } => "Quadrant II",
    { X: < 0, Y: < 0 } => "Quadrant III",
    { X: > 0, Y: < 0 } => "Quadrant IV",
    { X: 0 } or { Y: 0 } => "On axis",
    _ => "Origin"
};
Positional Patterns - deconstruction (C# 8+)

Positional patterns z records

🎉 C# 8 - Positional Patterns

(value1, value2) - pattern matching na deconstruction!

// Positional patterns - records mają automatyczną deconstruction
public record Point(int X, int Y);

string Classify(Point p) => p switch
{
    (0, 0) => "Origin",
    (0, _) => "On Y-axis",
    (_, 0) => "On X-axis",
    (var x, var y) when x == y => "On diagonal",
    (> 0, > 0) => "Quadrant I",
    _ => "Other"
};

Console.WriteLine(Classify(new Point(0, 0)));    // "Origin"
Console.WriteLine(Classify(new Point(0, 5)));    // "On Y-axis"
Console.WriteLine(Classify(new Point(3, 3)));    // "On diagonal"
Console.WriteLine(Classify(new Point(5, 10)));   // "Quadrant I"

Positional patterns z tuple

// Positional patterns z tuple
string GetRockPaperScissors((string player1, string player2) game) => game switch
{
    ("rock", "rock") => "Tie",
    ("rock", "paper") => "Player 2 wins",
    ("rock", "scissors") => "Player 1 wins",
    ("paper", "rock") => "Player 1 wins",
    ("paper", "paper") => "Tie",
    ("paper", "scissors") => "Player 2 wins",
    ("scissors", "rock") => "Player 2 wins",
    ("scissors", "paper") => "Player 1 wins",
    ("scissors", "scissors") => "Tie",
    _ => "Invalid input"
};

var game = ("rock", "scissors");
Console.WriteLine(GetRockPaperScissors(game));  // "Player 1 wins"

Positional + property patterns combined

// Łączenie positional i property patterns
public record Person(string Name, int Age)
{
    public string City { get; init; }
}

string Describe(Person person) => person switch
{
    // Positional pattern + property pattern
    ("Jan", var age) { City: "Warsaw" } => $"Jan, {age}, from Warsaw",
    
    // Positional with conditions
    (var name, > 18) => $"{name} is an adult",
    (var name, <= 18) => $"{name} is a minor",
    
    _ => "Unknown"
};

var p1 = new Person("Jan", 30) { City = "Warsaw" };
Console.WriteLine(Describe(p1));  // "Jan, 30, from Warsaw"
🔥 List Patterns - pattern matching na kolekcjach (C# 11)

Podstawowe list patterns

🎉 C# 11 - List Patterns

[pattern1, pattern2, ..] - pattern matching na elementach listy/array!

// List patterns - matching na array/list
int[] numbers = { 1, 2, 3, 4, 5 };

// Match specific elements
if (numbers is [1, 2, 3, 4, 5])
{
    Console.WriteLine("Exactly these numbers");
}

// Match with discard
if (numbers is [1, _, 3, _, 5])
{
    Console.WriteLine("1, something, 3, something, 5");
}

// Match with var (capture element)
if (numbers is [var first, var second, ..])
{
    Console.WriteLine($"First: {first}, Second: {second}");  // 1, 2
}

// Match any length
if (numbers is [..])
{
    Console.WriteLine("Any array (not null)");
}

Slice patterns - ..

// Slice pattern - .. captures rest
int[] GetArray() => new[] { 1, 2, 3, 4, 5 };

string DescribeArray(int[] arr) => arr switch
{
    [] => "Empty array",
    [var x] => $"Single element: {x}",
    [var first, var second] => $"Two elements: {first}, {second}",
    [var first, .., var last] => $"Multiple: first {first}, last {last}",
};

Console.WriteLine(DescribeArray(new[] { 1 }));        // "Single element: 1"
Console.WriteLine(DescribeArray(new[] { 1, 2 }));     // "Two elements: 1, 2"
Console.WriteLine(DescribeArray(new[] { 1, 2, 3, 4, 5 }));  // "Multiple: first 1, last 5"

List patterns - advanced

// Capture slice with var
int[] numbers = { 1, 2, 3, 4, 5 };

if (numbers is [var first, .. var rest])
{
    Console.WriteLine($"First: {first}");        // 1
    Console.WriteLine($"Rest: {string.Join(",", rest)}");  // 2,3,4,5
}

// Multiple slices
if (numbers is [var first, .. var middle, var last])
{
    Console.WriteLine($"First: {first}, Last: {last}");  // 1, 5
    Console.WriteLine($"Middle: {string.Join(",", middle)}");  // 2,3,4
}

// Match from end
if (numbers is [.., var secondLast, var last])
{
    Console.WriteLine($"Last two: {secondLast}, {last}");  // 4, 5
}

// Complex patterns
string AnalyzeSequence(int[] seq) => seq switch
{
    [] => "Empty",
    [_] => "Single element",
    [var x, var y] when x == y => "Two equal elements",
    [var x, var y] when x < y => "Ascending pair",
    [var x, var y] when x > y => "Descending pair",
    [1, ..] => "Starts with 1",
    [.., 10] => "Ends with 10",
    [1, .., 10] => "Starts with 1, ends with 10",
    [var first, .. var middle, var last] when first == last => "First equals last",
    _ => "Other sequence"
};

Console.WriteLine(AnalyzeSequence(new[] { 1, 2, 3, 10 }));  // "Starts with 1, ends with 10"

List patterns z type patterns

// List patterns + type patterns
object[] mixed = { 1, "hello", 3.14, true };

if (mixed is [int x, string s, double d, bool b])
{
    Console.WriteLine($"Int: {x}, String: {s}, Double: {d}, Bool: {b}");
}

// Switch expression z list patterns
string DescribeMixed(object[] arr) => arr switch
{
    [int, int, int] => "Three integers",
    [string, ..] => "Starts with string",
    [.., string] => "Ends with string",
    [int x, string s] => $"Int {x} and string {s}",
    _ => "Mixed types"
};
💡 Praktyczne przykłady - list patterns
// Przykład 1: Command parsing
string ProcessCommand(string[] args) => args switch
{
    ["help"] => ShowHelp(),
    ["version"] => ShowVersion(),
    ["run", var file] => RunFile(file),
    ["run", var file, "--debug"] => RunFile(file, debug: true),
    ["install", .. var packages] => Install(packages),
    _ => "Unknown command"
};

// Przykład 2: Path parsing
string GetFileInfo(string[] pathParts) => pathParts switch
{
    [] => "No path",
    [var file] => $"File: {file}",
    [.., var file] => $"File in subdirectory: {file}",
    ["home", "user", .. var rest] => $"User path: {string.Join("/", rest)}"
};

// Przykład 3: HTTP request routing
string Route(string[] segments) => segments switch
{
    ["api", "users"] => GetAllUsers(),
    ["api", "users", var id] => GetUser(id),
    ["api", "products", var id, "reviews"] => GetProductReviews(id),
    _ => "Not found"
};
Logical Patterns - and, or, not (C# 9+)

Logical patterns - łączenie warunków

🎉 C# 9 - Logical Patterns

and, or, not - łącz patterns jak warunki logiczne!

// and pattern - oba muszą być spełnione
int number = 15;

if (number is > 10 and < 20)
{
    Console.WriteLine("Between 10 and 20");
}

// or pattern - przynajmniej jeden
if (number is < 10 or > 100)
{
    Console.WriteLine("Less than 10 OR greater than 100");
}

// not pattern - negacja
if (number is not 0)
{
    Console.WriteLine("Not zero");
}

Logical patterns w switch expressions

// Logical patterns w switch
string ClassifyAge(int age) => age switch
{
    < 0 => "Invalid age",
    >= 0 and < 13 => "Child",
    >= 13 and < 20 => "Teenager",
    >= 20 and < 65 => "Adult",
    >= 65 => "Senior",
};

string ClassifyTemperature(double temp) => temp switch
{
    < 0 => "Freezing",
    >= 0 and < 10 => "Cold",
    >= 10 and < 20 => "Cool",
    >= 20 and < 30 => "Warm",
    >= 30 => "Hot"
};

Console.WriteLine(ClassifyAge(15));         // "Teenager"
Console.WriteLine(ClassifyTemperature(25)); // "Warm"

Łączenie logical patterns

// and + or
int x = 15;

if (x is (> 10 and < 20) or (> 30 and < 40))
{
    Console.WriteLine("In range 11-19 OR 31-39");
}

// not + and
if (x is not (< 0 or > 100))
{
    Console.WriteLine("Valid percentage (0-100)");
}

// Complex logical patterns
string ValidateInput(int value) => value switch
{
    < 0 => "Negative not allowed",
    >= 0 and <= 100 => "Valid",
    > 100 and <= 1000 => "High value",
    > 1000 => "Too high",
    _ => "Invalid"
};

Logical patterns z property patterns

// Logical patterns + property patterns
public record Product(string Name, decimal Price, string Category, bool InStock);

string GetProductStatus(Product product) => product switch
{
    // and z property patterns
    { Category: "Electronics", Price: > 1000 and < 5000 } 
        => "Mid-range electronics",
    
    // or z property patterns
    { Category: "Books" or "Magazines", InStock: true } 
        => "Available reading material",
    
    // not z property patterns
    { Category: not "Food", Price: > 10000 } 
        => "Expensive non-food item",
    
    // Complex combination
    { InStock: true, Price: > 100 and < 500 } and { Category: "Electronics" or "Appliances" }
        => "Available mid-price electronics/appliances",
    
    _ => "Other product"
};
Relational Patterns - porównania (C# 9+)

Relational patterns - <, >, <=, >=

🎉 C# 9 - Relational Patterns

<, >, <=, >= - porównania bezpośrednio w patterns!

// Relational patterns
int score = 85;

string GetGrade(int score) => score switch
{
    >= 90 => "A",
    >= 80 => "B",
    >= 70 => "C",
    >= 60 => "D",
    < 60 => "F"
};

Console.WriteLine(GetGrade(score));  // "B"

// z wartościami ujemnymi
string ClassifyNumber(int n) => n switch
{
    < -100 => "Very negative",
    >= -100 and < 0 => "Negative",
    0 => "Zero",
    > 0 and <= 100 => "Positive",
    > 100 => "Very positive"
};

Relational patterns z property patterns

// Relational + property patterns
public record Order(decimal Total, int ItemCount, bool IsPremium);

decimal CalculateShipping(Order order) => order switch
{
    { Total: >= 100 } => 0,                              // Free shipping
    { Total: >= 50, IsPremium: true } => 0,              // Premium free at 50
    { Total: >= 50 } => 5,                               // Reduced shipping
    { ItemCount: > 10 } => 10,                           // Bulk shipping
    { Total: < 20 } => 8,                                // Small order
    _ => 12                                               // Standard shipping
};

var order1 = new Order(120, 5, false);
Console.WriteLine(CalculateShipping(order1));  // 0 (free shipping)

var order2 = new Order(60, 3, true);
Console.WriteLine(CalculateShipping(order2));  // 0 (premium free at 50)

Relational patterns - praktyczne przykłady

// BMI classification
string ClassifyBMI(double bmi) => bmi switch
{
    < 18.5 => "Underweight",
    >= 18.5 and < 25 => "Normal weight",
    >= 25 and < 30 => "Overweight",
    >= 30 and < 35 => "Obese Class I",
    >= 35 and < 40 => "Obese Class II",
    >= 40 => "Obese Class III"
};

// HTTP status code classification
string ClassifyStatusCode(int code) => code switch
{
    >= 200 and < 300 => "Success",
    >= 300 and < 400 => "Redirection",
    >= 400 and < 500 => "Client Error",
    >= 500 => "Server Error",
    _ => "Invalid status code"
};

// Temperature comfort
string ComfortLevel(double celsius) => celsius switch
{
    < 10 => "Too cold",
    >= 10 and < 18 => "Cold",
    >= 18 and < 22 => "Comfortable",
    >= 22 and < 28 => "Warm",
    >= 28 => "Too hot"
};

Console.WriteLine(ClassifyBMI(22.5));           // "Normal weight"
Console.WriteLine(ClassifyStatusCode(404));     // "Client Error"
Console.WriteLine(ComfortLevel(20));            // "Comfortable"
Switch Expressions - pełna moc (C# 8+)

Switch statement vs switch expression

❌ Switch statement - verbose
string GetDayType(DayOfWeek day)
{
    string result;
    switch (day)
    {
        case DayOfWeek.Saturday:
        case DayOfWeek.Sunday:
            result = "Weekend";
            break;
        case DayOfWeek.Monday:
            result = "Start of week";
            break;
        case DayOfWeek.Friday:
            result = "End of week";
            break;
        default:
            result = "Weekday";
            break;
    }
    return result;
}

// 19 linii!
✅ Switch expression - zwięźle
string GetDayType(DayOfWeek day) => day switch
{
    DayOfWeek.Saturday or DayOfWeek.Sunday 
        => "Weekend",
    DayOfWeek.Monday => "Start of week",
    DayOfWeek.Friday => "End of week",
    _ => "Weekday"
};

// 6 linii! ✨

Switch expressions - składnia

// Switch expression składnia
// value switch { pattern => result, ... }

// Podstawowe użycie
string GetSeason(int month) => month switch
{
    12 or 1 or 2 => "Winter",
    3 or 4 or 5 => "Spring",
    6 or 7 or 8 => "Summer",
    9 or 10 or 11 => "Fall",
    _ => "Invalid month"
};

// Z type patterns
string DescribeType(object obj) => obj switch
{
    int i => $"Integer: {i}",
    string s => $"String of length {s.Length}",
    double d => $"Double: {d:F2}",
    null => "Null value",
    _ => "Unknown type"
};

// Z property patterns
record Person(string Name, int Age);

string Describe(Person p) => p switch
{
    { Age: < 13 } => "Child",
    { Age: < 20 } => "Teenager",
    { Age: >= 65 } => "Senior",
    _ => "Adult"
};

Switch expressions - all patterns combined

// Wszystkie patterns razem!
public record Order(int Id, string Status, decimal Total, List Items, Customer Customer);
public record Customer(string Name, bool IsPremium, int OrderCount);

string AnalyzeOrder(Order order) => order switch
{
    // Null check
    null => "No order",
    
    // Property + relational + logical
    { Status: "Cancelled" } => "Order cancelled",
    
    // List pattern + property
    { Items: [] } => "Empty order",
    { Items: [var single] } => $"Single item order: {single}",
    { Items: [var first, .., var last] } => $"Multiple items: {first}...{last}",
    
    // Nested property + relational
    { Customer.IsPremium: true, Total: > 1000 } 
        => "High-value premium order",
    
    { Customer: { OrderCount: > 10, IsPremium: false }, Total: > 500 }
        => "Consider upgrading to premium",
    
    // Property + relational + logical (complex)
    { Status: "Shipped", Total: >= 100 and < 500 }
        => "Standard shipped order",
    
    { Status: "Shipped", Total: >= 500 }
        => "Large shipped order",
    
    // Type pattern na nested object
    { Customer: { Name: var name } } when name.StartsWith("VIP")
        => $"VIP customer order: {name}",
    
    _ => "Regular order"
};

var order = new Order(
    1, 
    "Shipped", 
    250m, 
    new List { "Laptop", "Mouse", "Keyboard" },
    new Customer("John", false, 5)
);

Console.WriteLine(AnalyzeOrder(order));  // "Standard shipped order"

Switch expressions - praktyczne przykłady

// Przykład 1: Calculator
decimal Calculate(decimal a, decimal b, string op) => op switch
{
    "+" => a + b,
    "-" => a - b,
    "*" => a * b,
    "/" when b != 0 => a / b,
    "/" => throw new DivideByZeroException(),
    _ => throw new ArgumentException($"Unknown operator: {op}")
};

// Przykład 2: HTTP status message
string GetStatusMessage(int statusCode) => statusCode switch
{
    200 => "OK",
    201 => "Created",
    204 => "No Content",
    400 => "Bad Request",
    401 => "Unauthorized",
    403 => "Forbidden",
    404 => "Not Found",
    500 => "Internal Server Error",
    >= 200 and < 300 => "Success",
    >= 300 and < 400 => "Redirection",
    >= 400 and < 500 => "Client Error",
    >= 500 => "Server Error",
    _ => "Unknown Status"
};

// Przykład 3: Discount calculation
decimal GetDiscount(Customer customer, decimal total) => (customer, total) switch
{
    (null, _) => 0,
    ({ IsPremium: true }, > 1000) => 0.25m,
    ({ IsPremium: true }, _) => 0.15m,
    ({ OrderCount: > 20 }, > 500) => 0.20m,
    ({ OrderCount: > 10 }, _) => 0.10m,
    (_, > 500) => 0.05m,
    _ => 0
};

// Przykład 4: Rock Paper Scissors
string RockPaperScissors(string p1, string p2) => (p1, p2) switch
{
    ("rock", "rock") or ("paper", "paper") or ("scissors", "scissors") => "Tie",
    ("rock", "scissors") or ("scissors", "paper") or ("paper", "rock") => "Player 1 wins",
    ("scissors", "rock") or ("paper", "scissors") or ("rock", "paper") => "Player 2 wins",
    _ => "Invalid input"
};

Console.WriteLine(Calculate(10, 5, "+"));              // 15
Console.WriteLine(GetStatusMessage(404));              // "Not Found"
Console.WriteLine(RockPaperScissors("rock", "scissors"));  // "Player 1 wins"
Podsumowanie

Pattern Matching - od verbose if/else do zwięzłego, eleganci kodu!

  • Type patterns (C# 7) - is Type variable, type checking + assignment
  • Property patterns (C# 8) - { Property: value }, nested patterns
  • Positional patterns (C# 8) - (value1, value2), deconstruction
  • 🔥 List patterns (C# 11) - [first, .., last], slice patterns
  • Logical patterns (C# 9) - and, or, not
  • Relational patterns (C# 9) - <, >, <=, >=
  • 🔥 Switch expressions (C# 8) - value switch { pattern => result }
  • All patterns combined - potężne kombinacje!
  • Praktyczne przykłady - calculator, routing, validation, classification

W kolejnym wpisie skupimy się na Span<T> i Memory<T> - wydajność bez alokacji!

Zadanie dla Ciebie 🎯

Stwórz system routingu HTTP używając WSZYSTKICH patterns:

public record HttpRequest(string Method, string[] Path, Dictionary Query);
public record HttpResponse(int StatusCode, string Body);

// Zaimplementuj router używając switch expression + all patterns
HttpResponse Route(HttpRequest request)
{
    // GET /api/users -> list all users
    // GET /api/users/{id} -> get user by id
    // POST /api/users -> create user
    // GET /api/products?category=electronics -> filter products
    // DELETE /api/orders/{id} -> delete order
    // etc.
}

Wymagania - użyj:

  1. Switch expression jako główny router
  2. Property patterns na Method
  3. List patterns na Path (first, last, middle)
  4. Logical patterns (and, or, not)
  5. Relational patterns jeśli potrzeba
🎯 BONUS: Expression Evaluator z Pattern Matching

Stwórz evaluator dla wyrażeń matematycznych używając records + pattern matching!

Do zaimplementowania:

  1. Expression tree z records:
    • abstract record Expr;
    • record Constant(double Value) : Expr;
    • record Variable(string Name) : Expr;
    • record BinaryOp(Expr Left, string Op, Expr Right) : Expr;
    • record UnaryOp(string Op, Expr Operand) : Expr;
  2. Evaluate używając switch expression:
    • Type patterns na typy Expr
    • Property patterns na Op
    • Positional patterns na Left/Right
  3. Simplify (optimizer) używając patterns:
    • x + 0 → x
    • x * 1 → x
    • x * 0 → 0
    • (Constant a) + (Constant b) → Constant(a + b)
  4. ToString używając patterns:
    • Pretty print wyrażenia
    • Nawiasy gdy potrzeba

Przykład użycia:

// Build: (x + 5) * 2
var expr = new BinaryOp(
    new BinaryOp(
        new Variable("x"),
        "+",
        new Constant(5)
    ),
    "*",
    new Constant(2)
);

// Evaluate with x = 10
var vars = new Dictionary { ["x"] = 10 };
var result = Evaluate(expr, vars);  // (10 + 5) * 2 = 30

// Simplify
var simplified = Simplify(expr);    // Pattern match dla optimizations

// ToString
Console.WriteLine(expr.ToString());  // "(x + 5) * 2"

// More examples:
var zero = new BinaryOp(new Variable("x"), "*", new Constant(0));
var simplified = Simplify(zero);  // Constant(0) - eliminated x!

var identity = new BinaryOp(new Variable("x"), "+", new Constant(0));
var simplified2 = Simplify(identity);  // Variable("x") - eliminated +0!

Ten projekt demonstruje pełną moc pattern matching - type patterns, property patterns, positional patterns, switch expressions, wszystko razem! 🚀✨🧮