C# pattern matching

Type de pattern Exemple À quoi ça sert ?
Type pattern obj is string s Vérifier le type et extraire la variable typée
Constant pattern x is 0 Comparer directement avec une constante
Relational pattern age is > 18 Comparer avec <, >, <=, >=
Logical pattern age is >= 18 and < 65 Combiner plusieurs conditions logiques
Property pattern p is { Age: > 18, Pays: "FR" } Tester une ou plusieurs propriétés d'un objet
Positional pattern point is (0, 0) Déconstruire et matcher des valeurs via Deconstruct
List pattern (C# 11) [1, 2, ..] Faire matcher des tableaux ou des listes
Var pattern obj is var x Capturer une valeur sans faire de test

simple


object o = 42;

if (o is int i)                // motif de type + variable
    Console.WriteLine(i * 2);

if (o is 42)                   // motif de constante
    Console.WriteLine("C’est 42 !");

double x = 7.3;
if (x is > 0 and < 10)         // motifs relationnels + logiques (and/or/not)
    Console.WriteLine("Dans [0,10]");

switch expression + patterns relationnels / logiques


public static string QualifierAge(int age) =>
    age switch
    {
        < 0                => "Âge invalide",
        >= 0 and < 13      => "Enfant",
        >= 13 and < 18     => "Adolescent",
        >= 18 and < 65     => "Adulte",
        >= 65              => "Senior"
    };
    

Type pattern + discard + garde when


public static string Afficher(object obj) =>
    obj switch
    {
        null                       => "Null",
        string s when s.Length == 0 => "Chaîne vide",
        string s                   => $"Texte: {s}",
        int n when n % 2 == 0      => $"Entier pair: {n}",
        int n                      => $"Entier impair: {n}",
        _                          => $"Type inconnu: {obj.GetType().Name}"
    };

    

Property patterns (lecture structurée d'objets)


public record Personne(string Prenom, string Nom, Adresse Adresse);
public record Adresse(string Ville, string Pays);

public static string Decrire(Personne p) =>
    p switch
    {
        { Adresse: { Pays: "FR", Ville: "Paris" } } => "Parisien",
        { Adresse: { Pays: "FR" } }                 => "En France",
        { Adresse: { Pays: var pays } }             => $"À l'étranger ({pays})",
        _                                           => "Inconnu"
    };
    

Positional patterns (déconstruction)



public record Segment(Point A, Point B)
{
    public void Deconstruct(out double longueur, out bool horizontal)
    {
        longueur = Math.Sqrt(Math.Pow(B.X - A.X, 2) + Math.Pow(B.Y - A.Y, 2));
        horizontal = A.Y == B.Y;
    }
}
public record Point(double X, double Y);

public static string Qualifier(Segment s) =>
    s switch
    {
        ( <= 0, _ )         => "Segment nul",
        ( > 0 and <= 10, _) => "Court",
        ( > 10, true )      => "Long et horizontal",
        ( > 10, false )     => "Long"
    };

    

List patterns (C# 11)


public static string DecrireSerie(int[] xs) =>
    xs switch
    {
        []                        => "Liste vide",
        [0]                       => "Un zéro",
        [0, ..]                   => "Commence par zéro",
        [.., 0]                   => "Finit par zéro",
        [1, 2, 3]                 => "Exactement 1, 2, 3",
        [<= 0, .. var rest]       => $"Commence ≤ 0, reste longueur {rest.Length}",
        [.., > 100]               => "Se termine par > 100",
        [var first, .., var last] => $"Premier={first}, Dernier={last}",
    };
    

Combiner type patterns + property patterns + when


public interface IEvenement { DateTime DateUtc { get; } }
public record Achat(decimal Montant, string Devise, DateTime DateUtc) : IEvenement;
public record Connexion(string User, bool Reussie, DateTime DateUtc) : IEvenement;

public static string Router(IEvenement evt) =>
    evt switch
    {
        Achat { Montant: > 1000m, Devise: "EUR" } a 
            => $"Alerte: gros achat {a.Montant}€",
        Achat a when a.Devise is not "EUR" and not "USD" 
            => $"Achat devise exotique: {a.Devise}",
        Connexion { Reussie: false } c when c.User is "admin" or "root" 
            => "Alerte: échec admin",
        Connexion { Reussie: true, User: var u } 
            => $"Bienvenue {u}",
        _ => "Événement ignoré"
    };
    

Matching sur tuples (simple et efficace)


public static decimal PrixLivraison(string pays, int poidsKg) =>
    (pays, poidsKg) switch
    {
        ("FR", <= 1)         => 4.90m,
        ("FR", > 1 and <= 5) => 6.90m,
        ("FR", > 5)          => 9.90m,
        ("DE", <= 5)         => 7.90m,
        (_, <= 2)            => 12.00m,
        _                    => 20.00m
    };
    

not, and, or (logique moderne)


public static bool EstIdentifiantValide(string? id) =>
    id is not null and { Length: >= 3 } and not ("admin" or "root");

    

Guarded patterns : validation expressive


public static (bool ok, string? erreur) ValiderCommande(Commande c) =>
    c switch
    {
        { ClientId: null or "" }                 => (false, "ClientId requis"),
        { Lignes: [] }                           => (false, "Au moins une ligne"),
        { Total: <= 0 }                          => (false, "Total invalide"),
        { Adresse: { Pays: "US", Etat: null } }  => (false, "État requis pour US"),
        _                                        => (true, null)
    };

public record Commande(string ClientId, Ligne[] Lignes, decimal Total, Adresse Adresse);
public record Ligne(string Sku, int Qte);
public record Adresse(string Pays, string? Etat);
    

Exemples de refactoring d'if vers switch expression

avant

string Niveau(int score)
{
    if (score < 0) return "Invalide";
    if (score < 50) return "Débutant";
    if (score < 80) return "Intermédiaire";
    return "Avancé";
}
    
après

string Niveau(int score) => score switch
{
    < 0   => "Invalide",
    < 50  => "Débutant",
    < 80  => "Intermédiaire",
    _     => "Avancé"
};
    
au lieu de

if (evt is Achat a && a.Montant > 1000 && a.Devise == "EUR")
    Alert();
    
on a

    evt is Achat { Montant: > 1000, Devise: "EUR" }