switch expression și Pattern Matching în C# (.NET)

Bazele limbajului și I/O C# 8.0+ (list patterns în C# 11+)

Evaluează expresii concise bazate pe forma, tipul, proprietățile sau intervalele valorilor folosind pattern matching modern.

Sintaxă C#
var rezultat = valoare switch { tip { Prop: > 10 } x => ..., _ => valoareImplicita };

Ce face switch expression și Pattern Matching

Expresia switch (introdusă în C# 8 și extinsă masiv în C# 9–12) transformă instrucțiunea clasică switch / case / break într-o expresie funcțională compactă care returnează direct o valoare. Compilatorul verifică exhaustivitatea ramurilor: dacă nu acoperi toate cazurile posibile și omiți ramura implicită discard (_ => ...), primești un avertisment la compilare.

Combinată cu Pattern Matching (și cu operatorul is în condiții if), expresia switch poate testa simultan tipul unui obiect (type pattern), intervale numerice cu >, <=, and, or, not (relational & logical patterns), proprietăți interne ale obiectelor (property pattern: { Status: "Activ", Sold: > 1000m }), tupluri și chiar structura tablourilor sau a listelor (list patterns în C# 11: [var primul, .., var ultimul]).

Parametri, metode și opțiuni

Parametru / Metodă Descriere
val switch { ... } Evaluează val față de fiecare șablon în ordine și returnează expresia din dreapta săgeții =>.
> 0 and <= 100 Șabloane relaționale și logice (and, or, not) pentru testarea intervalelor fără a repeta variabila.
Tip { Prop: val } t Șablon de tip și proprietăți: verifică dacă obiectul este de tipul Tip, îi testează proprietățile și îl leagă la variabila t.
when conditie Gardă suplimentară (case guard) evaluată doar dacă șablonul principal s-a potrivit.
[primul, .. mijloc, ultimul] Șablon de listă (C# 11+): potrivește elemente din T[], List<T> sau Span<T> și extrage sub-secvențe cu ...
_ => ... Șablonul discard (implicit / default): prinde orice valoare care nu s-a potrivit pe ramurile anterioare.

Exemple

Exemplul 1: Calculul costului de livrare cu property, relational și tuple patterns

Verificăm simultan proprietățile unei comenzi (țară, valoare totală, abonament VIP) într-o singură expresie declarativă, fără lanțuri complexe de if / else if.

decimal CalculeazaTransport(Comanda c) => c switch
{
    { Anulata: true } => 0m,
    { MembruVip: true } => 0m,
    { Tara: "RO", Total: >= 200m } => 0m,
    { Tara: "RO", Total: < 200m } => 19.99m,
    { Tara: "MD" or "BG" or "HU" } => 45.00m,
    _ => 89.00m
};

var comenzi = new[]
{
    new Comanda("RO", 250m, MembruVip: false, Anulata: false),
    new Comanda("RO", 85m, MembruVip: false, Anulata: false),
    new Comanda("HU", 150m, MembruVip: false, Anulata: false),
    new Comanda("DE", 60m, MembruVip: true, Anulata: false)
};

foreach (var cmd in comenzi)
{
    Console.WriteLine($"{cmd.Tara} ({cmd.Total} RON, VIP={cmd.MembruVip}) -> Transport: {CalculeazaTransport(cmd)} RON");
}

record Comanda(string Tara, decimal Total, bool MembruVip, bool Anulata);

Ieșire în consolă

RO (250 RON, VIP=False) -> Transport: 0 RON
RO (85 RON, VIP=False) -> Transport: 19.99 RON
HU (150 RON, VIP=False) -> Transport: 45.00 RON
DE (60 RON, VIP=True) -> Transport: 0 RON

Exemplul 2: Pattern matching cu operatorul is not null și list patterns (C# 11+)

Putem analiza forma unui vector de argumente sau a unui rând CSV folosind șabloane de listă [..] și putem verifica absența lui null cu is not null.

string AnalizeazaComanda(string[] tokeni) => tokeni switch
{
    [] => "Comandă goală.",
    ["help"] or ["--help"] or ["-h"] => "Afișare meniu de ajutor.",
    ["deploy", var mediu] => $"Deploy în mediul '{mediu}' cu opțiuni implicite.",
    ["deploy", var mediu, .. var optiuni] => $"Deploy în '{mediu}' cu {optiuni.Length} opțiuni: {string.Join(", ", optiuni)}",
    _ => $"Comandă necunoscută: {tokeni[0]}"
};

Console.WriteLine(AnalizeazaComanda(new[] { "--help" }));
Console.WriteLine(AnalizeazaComanda(new[] { "deploy", "staging" }));
Console.WriteLine(AnalizeazaComanda(new[] { "deploy", "prod", "--force", "--no-backup" }));

Ieșire în consolă

Afișare meniu de ajutor.
Deploy în mediul 'staging' cu opțiuni implicite.
Deploy în 'prod' cu 2 opțiuni: --force, --no-backup

Greșeli frecvente

  • Plasarea unui șablon general înaintea unuia mai specific: dacă pui { Tara: "RO" } => 19.99m înainte de { Tara: "RO", Total: >= 200m } => 0m, compilatorul raportează eroarea CS8510: The pattern has already been handled by a previous arm of the switch expression.
  • Folosirea operatorului != null în loc de is not null atunci când clasa supraîncarcă operatorul == — șablonul is not null garantează verificarea strictă a referinței fără a apela operatori customizați.

Funcții și concepte înrudite

  • Tipuri Nullable (?, ??, ?.): Reprezintă explicit absența unei valori și previne excepțiile NullReferenceException prin operatori siguri de navigare și coalescență.
  • record și imutabilitate: Definește într-o singură linie modele de date imutabile cu egalitate bazată pe valoare, afișare automată și clonare nedistructivă (with).
  • var și tipurile de date (int, decimal, bool): Declară variabile puternic tipizate prin inferență la compilare (var) sau folosind tipurile fundamentale de valoare din .NET.