Une expression switch associe une valeur d'entrée à un résultat. Elle a été ajoutée en C# 8 et remplace le schéma courant d'une instruction switch dont chaque cas ne fait que renvoyer ou affecter quelque chose :
// C# 8 and later
string StatusText(int code) => code switch
{
200 => "OK",
301 or 302 => "Redirect", // 'or' needs C# 9
404 => "Not Found",
>= 500 => "Server Error", // relational patterns need C# 9
_ => "Unknown"
};
Lisez-la de haut en bas : la valeur placée avant switch est testée contre chaque branche dans l'ordre, et la première branche dont le pattern correspond fournit le résultat. Il n'y a ni case, ni break, ni mot-clé default ; _ (le pattern discard) correspond à tout et sert de valeur de repli.
En C# 7, la même correspondance est une instruction switch dans une petite méthode, avec return dans chaque cas :
Sortie :
200 OK
302 Redirect
404 Not Found
503 Server Error
418 Unknown
La forme expression est plus courte surtout parce que chaque branche tient sur une ligne et que le corps de la méthode disparaît. C'est aussi une expression, elle peut donc se placer partout où une valeur est attendue : un argument, un emplacement d'interpolation, un return, l'initialiseur d'un champ.
Règles de syntaxe
var result = input switch
{
pattern1 => value1,
pattern2 when condition => value2,
_ => fallback
};
- L'entrée vient en premier, puis
switch. C'est l'inverse de la forme instruction. - Chaque branche s'écrit
pattern => expression. La partie droite doit être une seule expression ; pour plus de logique, appelez une méthode. - Les branches sont séparées par des virgules. Une virgule finale après la dernière branche est autorisée.
- L'ensemble est une expression, donc une instruction qui l'utilise se termine par
;après l'accolade fermante. - Toutes les branches doivent produire des valeurs d'un type commun.
var x = n switch { 0 => "zero", _ => 0 };ne compile pas (CS8506, no best type). Depuis C# 9, un type cible déclaré tranche la question :object x = n switch { 0 => "zero", _ => 0 };compile. - Chaque branche doit produire une valeur, donc une branche ne peut pas appeler une méthode
voidcommeConsole.WriteLine, et une expression switch ne peut pas constituer une instruction à elle seule (CS0201). Pour des actions, utilisez l'instruction switch. - Une branche peut lever une exception au lieu de produire une valeur :
_ => throw new ArgumentOutOfRangeException(nameof(input)). - Le compilateur rejette une branche qui ne peut jamais être atteinte parce qu'une branche précédente la couvre déjà, avec l'erreur CS8510,
The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match.Placez les branches spécifiques avant les branches générales.
Patterns relationnels et logiques (C# 9)
C# 9 a ajouté <, <=, >, >= comme patterns, ainsi que les combinateurs and, or et not. Ils permettent d'écrire des tables d'intervalles qui se lisent comme une spécification :
// C# 9 and later
string Grade(int score) => score switch
{
< 0 or > 100 => throw new ArgumentOutOfRangeException(nameof(score)),
>= 90 => "A",
>= 80 => "B",
>= 70 => "C",
>= 60 => "D",
_ => "F"
};
string AgeGroup(int age) => age switch
{
< 13 => "child",
>= 13 and <= 19 => "teen",
_ => "adult"
};
bool IsLetter(char c) => c is >= 'a' and <= 'z' or >= 'A' and <= 'Z';
Comme les branches sont essayées dans l'ordre, >= 80 ne voit que les notes inférieures à 90. Les patterns relationnels n'acceptent que des constantes à droite ; pour comparer à une variable, utilisez une garde when. L'équivalent C# 7 d'une table d'intervalles est une chaîne de if :
Sortie :
95 -> A
83 -> B
71 -> C
64 -> D
12 -> F
not s'utilise le plus souvent sous la forme not null : x is not null, ou une branche not null => x.Name.
Patterns de propriété
Un pattern de propriété teste les membres d'un objet : { Property: pattern, ... }. Combiné à une expression switch, il exprime des règles métier sans chaîne de conditions && :
// C# 8 and later
decimal ShippingCost(Order order) => order switch
{
{ Total: >= 100m } => 0m, // relational inside: C# 9
{ Country: "US", Express: true } => 15m,
{ Country: "US" } => 5m,
{ Express: true } => 30m,
null => throw new ArgumentNullException(nameof(order)),
_ => 12m
};
Chaque propriété listée doit correspondre pour que la branche corresponde. Un pattern de propriété vide { } correspond à toute valeur non nulle. C# 10 a ajouté les patterns de propriété étendus pour les membres imbriqués : { Customer.Address.Country: "US" } au lieu de { Customer: { Address: { Country: "US" } } }.
Patterns de tuple
Faire un switch sur un tuple teste plusieurs valeurs à la fois, ce qui est la façon propre d'exprimer une table de décision :
// C# 9 and later (the 'or' pattern)
string Winner(string a, string b) => (a, b) switch
{
("rock", "scissors") or ("scissors", "paper") or ("paper", "rock") => "player 1",
var (x, y) when x == y => "draw",
_ => "player 2"
};
var (x, y) déconstruit le tuple en deux variables que la garde when peut comparer. En C# 7, le substitut habituel consiste à combiner les valeurs en une seule clé, ou à utiliser un dictionnaire des résultats :
Sortie :
player 1
player 2
draw
Patterns de type et when
Une branche peut tester le type à l'exécution et lier une variable, et une clause when ajoute toute condition que la syntaxe des patterns ne peut pas exprimer :
// C# 8 and later
double Area(Shape shape) => shape switch
{
Circle c => Math.PI * c.Radius * c.Radius,
Rectangle r when r.Width == r.Height => r.Width * r.Width,
Rectangle r => r.Width * r.Height,
null => throw new ArgumentNullException(nameof(shape)),
_ => throw new NotSupportedException(shape.GetType().Name)
};
La version C# 7 utilise le pattern de type is dans une chaîne de if, qui lie la variable de la même façon :
Sortie :
Circle: 12.57
Rectangle: 13.50
Exhaustivité et SwitchExpressionException
Une expression switch doit produire une valeur pour chaque entrée. Quand le compilateur voit des entrées qu'aucune branche ne couvre, il émet un avertissement :
warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '0' is not covered.
C'est un avertissement, pas une erreur, donc le code compile quand même. Si une valeur sans correspondance arrive à l'exécution, l'expression lève System.Runtime.CompilerServices.SwitchExpressionException, dont le message se termine par la valeur non couverte (Unmatched value was 3.).
Les enums sont un cas particulier. Même quand chaque membre nommé a sa branche, une variable enum peut contenir n'importe quel entier ((Size)3), donc le compilateur signale CS8524 pour « an unnamed enum value » :
// C# 8 and later
enum Size { Small, Medium, Large }
string Code(Size s) => s switch
{
Size.Small => "S",
Size.Medium => "M",
Size.Large => "L",
// warning CS8524 without the next arm
_ => throw new ArgumentOutOfRangeException(nameof(s))
};
Ajoutez une branche _ explicite qui lève une exception utile. Cela documente l'hypothèse, fait taire l'avertissement et donne une erreur plus claire que SwitchExpressionException si une mauvaise valeur apparaît un jour.
Un dictionnaire comme alternative
Quand les branches ne sont que des paires constante et valeur et que la table peut grandir ou venir de la configuration, un Dictionary fait le même travail dans toutes les versions de C# et peut être construit à l'exécution :
Sortie :
19.00
11.50
0
Un pays inconnu reçoit un taux de 0m. Un produit de decimal a autant de décimales que ses deux opérandes réunis, c'est pourquoi 100m * 0.19m affiche 19.00 et le taux nul affiche 0.
Expression switch ou instruction switch
| instruction switch | expression switch | |
|---|---|---|
| Disponible depuis | C# 1 | C# 8 |
| Produit une valeur | non | oui |
| Syntaxe par branche | case X: ... break; | X => value, |
| Repli | default: | _ => |
| Plusieurs instructions par branche | oui | non, appelez une méthode |
| Avertissement d'exhaustivité | non | oui (CS8509) |
| Aucune correspondance à l'exécution | rien ne s'exécute | SwitchExpressionException |
Les deux formes acceptent les mêmes patterns (types, constantes, propriétés, tuples, relationnels), donc apprendre l'une enseigne l'autre. Le choix dépend de la forme : une valeur passe par l'expression, une suite d'actions par l'instruction.
Questions fréquentes
Qu'est-ce qu'une expression switch en C# ?
Une expression switch, ajoutée en C# 8, s'évalue en une valeur : string text = code switch { 200 => "OK", 404 => "Not Found", _ => "Unknown" };. L'entrée se place avant le mot-clé switch, chaque branche s'écrit pattern => result, les branches sont séparées par des virgules, et il n'y a pas de mot-clé case, break ou default.
Quel est le cas par défaut d'une expression switch C# ?
Le pattern discard _ correspond à tout, donc _ => "Unknown" en dernière branche joue le rôle de default. Sans branche fourre-tout, le compilateur émet un avertissement (CS8509) quand une entrée n'est pas couverte, et une valeur sans correspondance à l'exécution lève SwitchExpressionException.
Comment faire correspondre plusieurs valeurs dans une même branche d'une expression switch ?
Utilisez le pattern or de C# 9 : "sat" or "sun" => "weekend". Pour les intervalles, combinez des patterns relationnels avec and : >= 13 and <= 19 => "teen". En C# 8, on liste les valeurs dans des branches séparées avec le même résultat.
Peut-on utiliser when dans une expression switch C# ?
Oui. Une branche peut ajouter une garde après son pattern : Order o when o.Total > 100 => 0m. La branche ne correspond que si le pattern correspond et que la condition when est vraie ; sinon, l'évaluation continue avec la branche suivante.
Faut-il utiliser une expression switch ou une instruction switch ?
Utilisez l'expression quand chaque branche ne fait que produire une valeur, comme associer un statut à un libellé ou calculer un prix. Utilisez l'instruction quand les branches exécutent plusieurs instructions, ont des effets de bord ou doivent sortir d'une boucle. L'expression vous donne aussi des avertissements d'exhaustivité que l'instruction ne donne pas.