Le pattern matching (filtrage par motif) teste une valeur par rapport à une forme : « est-ce un Circle ? », « est-ce null ? », « son Status vaut-il "paid" et son Total dépasse-t-il 100 ? ». Quand le test réussit, le pattern peut aussi extraire des parties de la valeur dans de nouvelles variables. Il a commencé modestement en C# 7 avec l'opérateur is et s'est enrichi à chaque version depuis, c'est pourquoi cette page indique la version de chaque forme.
Le pattern de type avec is (C# 7)
Avant C# 7, vérifier un type puis l'utiliser demandait deux étapes : as plus un test de null, ou is plus un cast. Le pattern de type fait les deux à la fois et vous donne une variable typée :
Sortie :
card ending 4242: 49.90
transfer from PT50...: 1200
no payment
other payment: 5
p is CardPayment card n'est vrai que lorsque p est un CardPayment non null (ou une classe qui en dérive), et card le contient alors avec le bon type statique. Une valeur null ne correspond jamais à un pattern de type, c'est pourquoi le paiement null tombe jusqu'au test de null.
Portée et affectation définie
La variable du pattern existe dans le bloc englobant, mais le compilateur ne vous laisse la lire que là où la correspondance est connue pour avoir réussi. C'est ce qui fait fonctionner la forme « clause de garde », où l'on sort tôt et utilise la variable ensuite :
Sortie :
19.99
42 is over 40
price must be text
boxed is int n && n > 40 montre l'autre forme courante : la variable est utilisable à droite de &&, car ce côté ne s'exécute que si la correspondance a réussi. À droite de ||, elle n'est pas affectée, et le compilateur le signale (CS0165).
Les patterns font aussi l'unboxing : boxed is int n réussit pour un int boxé et vous donne la valeur sans cast susceptible de lever une exception. Pour les types valeur nullables, maybe is int n réussit exactement quand maybe a une valeur.
Patterns de constante et de null
Une constante est aussi un pattern. x is null est le plus utilisé, et C# 7 autorise aussi x is 0, status is "paid" ou un membre d'enum :
if (customer is null) return; // null check that ignores any == overload
if (count is 0) Console.WriteLine("empty");
if (level is LogLevel.Error) Alert();
Beaucoup d'équipes préfèrent is null à == null, car il ne peut pas être détourné par une surcharge d'opérateur du type. C# 9 ajoute la forme négative, is not null.
Patterns dans les instructions switch (C# 7)
Les mêmes patterns de type peuvent servir d'étiquettes case, et une clause when ajoute une condition. Les cas sont essayés de haut en bas, donc classez-les du plus spécifique au plus général :
static string Describe(object o)
{
switch (o)
{
case null: return "nothing";
case int n when n < 0: return "negative number";
case int n: return $"number {n}";
case string s when s.Length == 0: return "empty text";
case string s: return $"text of {s.Length} chars";
case IEnumerable<int> list: return "a sequence of numbers";
default: return o.GetType().Name;
}
}
Describe(-4); // negative number
Describe(""); // empty text
Describe(new List<int>()); // a sequence of numbers
Describe(2.5); // Double
Le compilateur vérifie l'ordre : un cas qui ne peut jamais correspondre parce qu'un cas précédent le couvre donne l'erreur CS8120. default est toujours évalué en dernier, où qu'il apparaisse. La même logique écrite avec des chaînes de if/is, comme dans l'exemple de paiement ci-dessus, fonctionne dans toutes les versions de C# à partir de 7.
Expressions switch (C# 8)
Une expression switch est la forme compacte de « calculer une valeur à partir de plusieurs cas ». La valeur vient d'abord, puis switch, puis des branches pattern => result séparées par des virgules, avec _ comme fourre-tout :
public abstract record Shape;
public record Circle(double Radius) : Shape;
public record Rectangle(double Width, double Height) : Shape;
static double Area(Shape shape) => shape switch
{
Circle c => Math.PI * c.Radius * c.Radius,
Rectangle { Width: var w, Height: var h } => w * h,
_ => throw new ArgumentException("Unknown shape", nameof(shape)),
};
Area(new Rectangle(3, 4)); // 12
Si aucune branche ne correspond à l'exécution, elle lève SwitchExpressionException, et le compilateur avertit (CS8509) quand il voit qu'une entrée n'est pas traitée. La page sur l'expression switch en couvre la syntaxe ; la suite de cette page porte sur les patterns que l'on peut placer dans les branches.
Patterns de propriété (C# 8, étendus en C# 10)
Un pattern de propriété compare les propriétés d'un objet à des patterns imbriqués, entre accolades. L'objet doit aussi être non null, donc { } seul signifie « non null » :
public record Address(string City, string Country);
public record Order(decimal Total, string Status, Address ShipTo, int Items);
static string Shipping(Order order) => order switch
{
{ Status: "cancelled" } => "no shipment",
{ ShipTo.Country: "PT", Total: >= 50m } => "free, domestic", // C# 10 dotted form
{ ShipTo.Country: "PT" } => "4.90, domestic",
{ Total: > 200m } => "free, international",
_ => "12.00, international",
};
Shipping(new Order(60m, "paid", new Address("Porto", "PT"), 2)); // free, domestic
Shipping(new Order(260m, "paid", new Address("Lyon", "FR"), 2)); // free, international
En C# 8 et 9, la forme imbriquée est obligatoire : { ShipTo: { Country: "PT" } }. C# 10 autorise le raccourci à points utilisé ci-dessus. Les patterns de propriété fonctionnent aussi avec is, ce qui fait se lire les vérifications multiples comme une description : if (order is { Status: "paid", Items: > 0 }).
Patterns relationnels et logiques (C# 9)
C# 9 a ajouté les comparaisons (<, <=, >, >=) et les combinateurs and, or et not, qui transforment les intervalles en patterns :
static string Grade(int score) => score switch
{
< 0 or > 100 => "invalid",
>= 90 => "A",
>= 75 and < 90 => "B",
>= 50 => "C",
_ => "F",
};
Grade(82); // B
Grade(101); // invalid
if (input is not null and not "") { ... }
if (c is >= 'a' and <= 'z' or >= 'A' and <= 'Z') { ... } // and binds tighter than or
L'équivalent en C# 7 est une chaîne if/else if ou un switch avec des clauses when (case int s when s >= 90:).
Patterns de tuple et positionnels (C# 8)
Faire un switch sur plusieurs valeurs à la fois se fait en le faisant sur un tuple :
static string Quadrant(int x, int y) => (x, y) switch
{
(0, 0) => "origin",
(> 0, > 0) => "I",
(< 0, > 0) => "II",
(< 0, < 0) => "III",
(> 0, < 0) => "IV",
_ => "on an axis",
};
Quadrant(-2, 5); // II
Un pattern positionnel fait la même chose pour tout type doté d'une méthode Deconstruct, records compris : case Point(0, 0): ou p is Rectangle(var w, var h) && w == h.
Patterns de liste (C# 11)
Les patterns de liste font correspondre des tableaux et des listes selon leurs éléments. .. correspond à un nombre quelconque d'éléments (une « tranche »), et chaque position d'élément peut contenir n'importe quel autre pattern :
static string Route(string[] args) => args switch
{
[] => "help",
["add", var item] => $"add {item}",
["remove", var item, ..] => $"remove {item}",
[var cmd, ..] => $"unknown command {cmd}",
};
Route(new[] { "add", "milk" }); // add milk
Route(new[] { "remove", "milk", "now" }); // remove milk
int[] nums = { 1, 2, 3, 4 };
if (nums is [1, .., var last]) Console.WriteLine(last); // 4
Quelle version a ajouté quoi
| Pattern | Exemple | Version |
|---|---|---|
| Pattern de type avec variable | o is Circle c | C# 7.0 |
| Pattern de constante | x is null, n is 0 | C# 7.0 |
Pattern var | x is var v | C# 7.0 |
Patterns dans les étiquettes case, gardes when | case int n when n < 0: | C# 7.0 |
Expression switch, discard _ | x switch { ... } | C# 8 |
| Pattern de propriété | { Status: "paid" } | C# 8 |
| Pattern de tuple | (x, y) switch { (0, 0) => ... } | C# 8 |
| Pattern positionnel | Point(0, 0) | C# 8 |
| Pattern relationnel | > 100, <= 0 | C# 9 |
| Patterns logiques | not null, >= 1 and <= 5, 'a' or 'b' | C# 9 |
| Pattern de type seul | case Circle:, Circle => ... | C# 9 |
| Pattern de propriété étendu | { ShipTo.City: "Porto" } | C# 10 |
| Pattern de liste et tranche | [first, .., last] | C# 11 |
Erreurs courantes
- Classer les cas du général au spécifique.
case Payment p:avantcase CardPayment c:rend le second inaccessible (CS8120). Le spécifique d'abord. - S'attendre à ce qu'un pattern de type corresponde à null.
null is string sest faux. Traiteznullavec son propre cas ouis null. - Lire la variable du pattern là où la correspondance a pu échouer. Après
a is int n || ...,nn'est pas affectée (CS0165). - De longs switch sur les types au lieu de méthodes virtuelles. Si chaque nouvelle sous-classe oblige à modifier le même switch à cinq endroits, le comportement appartient à la hiérarchie de classes. Les patterns brillent pour les données venant de l'extérieur (formes JSON, messages, tuples d'entrées) et pour les ensembles fermés de types que vous contrôlez.
- Oublier le fourre-tout dans une expression switch. Une entrée sans correspondance lève une exception à l'exécution ; lisez l'avertissement CS8509.
Questions fréquentes
Qu'est-ce que le pattern matching en C# ?
Le pattern matching teste une valeur par rapport à une forme et, quand elle correspond, peut en extraire des parties dans des variables au cours de la même étape. if (shape is Circle c) teste le type et vous donne un c typé ; order is { Status: "paid", Total: > 100 } teste des propriétés. Les patterns apparaissent dans les expressions is, les instructions switch et les expressions switch.
Comment utiliser is avec une variable en C# ?
if (obj is Customer c) { ... } vérifie que obj est un Customer (non null) et l'affecte à c, typé Customer, dans le if. Cela remplace l'ancien couple var c = obj as Customer; if (c != null). La variable n'est définitivement affectée que là où le test est connu pour être vrai, donc if (!(obj is Customer c)) return; laisse c utilisable après le if.
Quelle est la différence entre is null et == null en C# ?
x is null vérifie toujours la référence elle-même. x == null appelle l'opérateur == du type s'il en surcharge un, ce qui donne en général la même réponse mais sans garantie. Pour l'inverse, écrivez x != null, !(x is null), ou x is not null depuis C# 9.
Comment fonctionne le pattern matching de switch avec when en C# ?
Une étiquette case peut contenir un pattern plus une garde when : case Order o when o.Total > 100:. Le cas ne correspond que si le pattern correspond et que la condition est vraie, et les cas sont essayés de haut en bas, donc placez les plus spécifiques en premier. Le compilateur signale CS8120 quand un cas ne peut jamais être atteint parce qu'un cas précédent le couvre déjà.
Quelle version de C# a ajouté quels patterns ?
C# 7.0 : patterns de type, de constante et var dans is et case, plus when. C# 8 : expressions switch, patterns de propriété, de tuple et positionnels. C# 9 : patterns relationnels (> 5), logiques (and, or, not) et de type seul. C# 10 : patterns de propriété étendus ({ Address.City: "Porto" }). C# 11 : patterns de liste ([1, .., var last]).