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" }