1. L’opérateur is : checker la compatibilité des types
Dans les applis réelles, ça arrive souvent que t’aies une référence vers un objet du type de base, mais tu sais (ou tu supposes) que l’objet en mémoire est en fait un type dérivé plus précis. Dans ces cas-là, tu peux avoir besoin d’accéder à des membres spécifiques de ce type dérivé. Pour gérer ça proprement et sans danger, C# te file plusieurs outils clés : les opérateurs is, as et, surtout dans les versions récentes du langage, les puissantes features de pattern matching.
is te permet de vérifier si un objet est une instance d’un type donné, d’un de ses types dérivés ou s’il implémente une interface donnée. Il renvoie true si l’objet est compatible avec ce type, et false sinon.
Utilisation principale : savoir si tu peux caster un objet vers un type donné sans risque.
Exemple : Checker le type de base et l’héritage
class Animal { public string Espece { get; set; } = "Inconnu"; }
class Chien : Animal { public void Aboyer() => Console.WriteLine("Ouaf !"); }
class Chat : Animal { public void Miauler() => Console.WriteLine("Miaou !"); }
Animal monAnimal = new Chien { Espece = "Golden Retriever" };
Console.WriteLine($"monAnimal is Animal: {monAnimal is Animal}"); // True
Console.WriteLine($"monAnimal is Chien: {monAnimal is Chien}"); // True
Console.WriteLine($"monAnimal is Chat: {monAnimal is Chat}"); // False
Dans cet exemple, monAnimal est en fait un Chien. Donc monAnimal is Animal et monAnimal is Chien vont renvoyer true.
Exemple : Checker null
L’opérateur is se comporte aussi comme prévu avec null.
Animal animalNull = null;
Chien chienSpecifique = new Chien();
Console.WriteLine($"animalNull is Animal: {animalNull is Animal}"); // False (null n’est instance d’aucun type)
Console.WriteLine($"chienSpecifique is null: {chienSpecifique is null}"); // False (l’objet n’est pas null)
Note que null n’est instance d’aucun type, donc null is MonType sera toujours false. Par contre, unObjet is null c’est un moyen simple et safe de checker si la référence null.
Exemple : Utiliser is avec des interfaces
L’opérateur is marche aussi pour checker si un objet implémente une interface.
interface IVolant { void Voler(); }
class Oiseau : Animal, IVolant { public void Voler() => Console.WriteLine("Battement d’ailes !"); }
class Poisson : Animal { }
Animal creature = new Oiseau();
Console.WriteLine($"creature is Oiseau: {creature is Oiseau}"); // True
Console.WriteLine($"creature is IVolant: {creature is IVolant}"); // True
creature = new Poisson();
Console.WriteLine($"creature is IVolant: {creature is IVolant}"); // False
2. L’opérateur as : cast safe des types
L’opérateur as sert à caster un objet vers un type donné sans danger. Contrairement au cast explicite (Type)obj qui balance une InvalidCastException si ça foire, as renvoie null si la conversion n’est pas possible. C’est parfait quand t’es pas sûr du type réel de l’objet.
Utilisation principale : Essayer de caster, et récupérer null si ça ne marche pas.
Exemple : Utilisation de base de as
class Forme { }
class Cercle : Forme { public double Rayon { get; set; } }
class Carre : Forme { public double Cote { get; set; } }
Forme maForme = new Cercle { Rayon = 5.0 };
// Essayer de caster en Cercle
Cercle cercle = maForme as Cercle;
if (cercle != null) // Toujours checker null !
{
Console.WriteLine($"C’est un cercle de rayon : {cercle.Rayon}"); // Affiche : C’est un cercle de rayon : 5
}
// Essayer de caster en Carre
Carre carre = maForme as Carre;
if (carre == null) // Cast raté, carre == null
{
Console.WriteLine("Ce n’est pas un carré."); // Affiche : Ce n’est pas un carré.
}
Comme tu vois, as évite les plantages à l’exécution, il renvoie null au lieu d’une exception.
Exemple : Limitations de as
Faut pas oublier que l’opérateur as marche seulement avec les types référence et les types valeur nullable. Il ne marche pas avec les types valeur classiques, vu qu’ils ne peuvent pas être null.
object uneValeur = 100;
int num = uneValeur as int; // ERREUR DE COMPIL : 'as' ne marche pas avec les types non-nullable
int? numNullable = uneValeur as int?; // OK, int nullable
Console.WriteLine($"Int nullable : {numNullable}"); // Affiche : Int nullable : 100
string str = uneValeur as string; // Renvoie null, car 100 n’est pas une string
Console.WriteLine($"String depuis int : {str ?? "null"}"); // Affiche : String depuis int : null
Pour les types valeur non-nullable, utilise soit le cast explicite ((int)uneValeur) si t’es sûr du type (et prêt à gérer une exception), soit, mieux, le pattern matching.
3. Pattern Matching (Correspondance de motif)
Le pattern matching, c’est une feature puissante et en pleine évolution qui te file des moyens plus élégants et safe de checker les types et d’extraire des données des objets. Ça réduit vachement le code répétitif et rend tout plus lisible.
Type Pattern (Motif de type) avec is
C’est la forme la plus courante du pattern matching : tu vérifies le type d’un objet et, si ça matche, tu l’affectes direct à une nouvelle variable du bon type.
Syntaxe : expression is Type variable
Exemple : Remplacer is + cast
// Toujours avec les classes Forme, Cercle, Carre
Forme formeActuelle = new Cercle { Rayon = 7.5 };
// Ancienne méthode, un peu lourde :
if (formeActuelle is Cercle)
{
Cercle c = (Cercle)formeActuelle;
Console.WriteLine($"Ancienne méthode : Cercle de rayon {c.Rayon}");
}
// Nouvelle méthode, plus clean avec le type pattern
if (formeActuelle is Cercle c) // Check le type et crée la variable 'c'
{
Console.WriteLine($"Nouvelle méthode : Cercle de rayon {c.Rayon}"); // 'c' est déjà du type Cercle
}
Forme autreForme = new Carre { Cote = 10.0 };
if (autreForme is Carre s)
{
Console.WriteLine($"C’est un carré de côté {s.Cote}");
}
La variable c (ou s) n’existe que dans le bloc if, donc tu risques pas de l’utiliser au mauvais endroit.
Property Pattern (Motif de propriété)
Depuis C# 8.0, tu peux non seulement checker le type, mais aussi checker direct la valeur d’une ou plusieurs propriétés de l’objet, et même les extraire dans de nouvelles variables.
Syntaxe : expression is Type { Propriete1: motif_valeur, Propriete2: variable_extraction }
Exemple : Checker une propriété
Forme formeTest = new Cercle { Couleur = "Vert", Rayon = 12.0 };
// On check si c’est un cercle et si sa couleur est verte
if (formeTest is Cercle { Couleur: "Vert" })
{
Console.WriteLine("Cercle vert trouvé.");
}
// On check si c’est un cercle et on extrait son rayon
if (formeTest is Cercle { Rayon: var r })
{
Console.WriteLine($"Rayon extrait : {r}");
}
// On combine check et extraction :
if (formeTest is Cercle { Couleur: "Vert", Rayon: var rayonVal } objCercle)
{
Console.WriteLine($"Cercle vert extrait. Rayon : {rayonVal}, Objet : {objCercle.Rayon}");
}
Le property pattern simplifie grave les conditions compliquées.
Exemple : Intervalles et opérateurs logiques dans les Property Patterns
Maintenant tu peux checker que les propriétés d’un objet respectent certaines conditions :
Cercle grandCercle = new Cercle { Rayon = 25.0, Couleur = "Bleu" };
// On check si le rayon est > 20 ET la couleur bleue
if (grandCercle is Cercle { Rayon: > 20, Couleur: "Bleu" })
{
Console.WriteLine("Gros cercle bleu détecté.");
}
// On check si le rayon est dans l’intervalle (5..15)
if (formeTest is Cercle { Rayon: >= 5 and <= 15 })
{
Console.WriteLine("Cercle de taille moyenne.");
}
Important ! Contrairement au type bool, ici on utilise les mots-clés : and, or et not.
4. Switch Expressions et Switch Statements avec Pattern Matching
Le pattern matching est encore plus puissant dans les switch. Tu peux faire des logiques différentes selon le motif qui matche.
Exemple : Switch Statement
Tu peux définir des blocs de code pour chaque motif qui matche.
// Les classes Animal, Chien, Chat comme au début
Animal creatureActuelle = new Chien { Espece = "Caniche" };
switch (creatureActuelle)
{
case Chien d: // Motif de type : si c’est un Chien, assigné à 'd'
d.Aboyer();
Console.WriteLine($"C’est un chien de race {d.Espece}.");
break;
case Chat c when c.Espece == "Siamois": // Motif de type avec condition (when)
c.Miauler();
Console.WriteLine($"C’est un chat siamois.");
break;
case Animal a: // Si c’est juste un Animal (pas Chien/Chat)
Console.WriteLine($"C’est juste un animal de type {a.Espece}.");
break;
case null: // Motif null pour gérer les valeurs null
Console.WriteLine("Objet égal à null.");
break;
default: // Si aucun motif ci-dessus ne matche
Console.WriteLine("Créature inconnue.");
break;
}
L’ordre des cases dans le switch compte : les motifs les plus précis doivent venir avant les plus généraux.
Exemple : Switch Expression
C’est une syntaxe plus compacte pour switch qui renvoie une valeur. Parfait pour transformer un objet en une autre valeur selon son type ou ses propriétés.
Syntaxe : expression switch { motif1 => resultat1, motif2 => resultat2, ... _ => resultat_defaut }
Forme formeAtraiter = new Rectangle { Largeur = 5, Hauteur = 5, Couleur = "Rouge" };
string infoForme = formeAtraiter switch
{
Cercle { Rayon: var r } when r > 10 => $"Gros cercle (R={r})", // Motif propriété avec condition
Cercle { Couleur: "Bleu" } c => $"Cercle bleu (R={c.Rayon})", // Motif propriété avec extraction
Cercle c => $"Cercle normal (R={c.Rayon})", // Motif de type simple
Rectangle { Largeur: var l, Hauteur: var h } when l == h => $"Carré ({l}x{h})", // Carré
Rectangle r => $"Rectangle ({r.Largeur}x{r.Hauteur})", // Tout autre rectangle
null => "Forme inexistante (null)", // Motif null
_ => "Forme inconnue" // Motif joker (_), comme 'default'
};
Console.WriteLine(infoForme); // Affiche : Carré (5x5)
switch expression est super pratique pour des conversions courtes et expressives.
Var Pattern (Motif var)
Depuis C# 7.0, tu peux utiliser var dans le pattern matching. var matche toujours n’importe quel objet (sauf null) et l’extrait dans une variable du bon type.
Exemple : Utiliser le motif var
object obj = "Hello World";
if (obj is var resultat) // Toujours true, resultat sera string
{
Console.WriteLine($"Type : {resultat.GetType().Name}, Valeur : {resultat}");
}
obj = 123;
string nomType = obj switch
{
var x when x is int => "C’est un entier",
var y when y is string => "C’est une chaîne",
_ => "Autre chose"
};
Console.WriteLine(nomType); // Affiche : C’est un entier
Le motif var est rarement utilisé tout seul pour checker le type, mais il est super utile dans d’autres motifs ou dans un switch pour extraire la valeur.
5. is vs as vs Pattern Matching : quand utiliser quoi ?
Le choix de l’outil dépend de ce que tu veux faire et de la version de C# que tu utilises.
L’opérateur is (simple) :
À utiliser : Quand tu veux juste checker si un objet est d’un type donné, et que t’as pas besoin de caster ou d’accéder à des membres spécifiques tout de suite.
if (monObjet is UnType)
L’opérateur as :
À utiliser : Quand tu veux caster un type référence (ou un type valeur nullable) et gérer l’échec sans exception, en checkant null.
MaClasseDerivee derive = monObjetBase as MaClasseDerivee;
if (derive != null) { /* ... */ }
Pattern Matching (is Type variable) :
La méthode moderne à privilégier : Combine le check du type et le cast safe en une seule ligne, plus lisible. Marche pour tous les types.
À utiliser : Quand tu veux checker le type et utiliser direct les membres spécifiques de ce type.
if (monObjet is MaClasseDerivee derive) { derive.MethodeSpecifique(); }
Pattern Matching (switch expressions / statements) :
La méthode moderne à privilégier : Quand tu veux faire des actions différentes ou retourner des valeurs différentes selon le type de l’objet, ses propriétés ou d’autres trucs. C’est bien plus clean que des chaînes de if-else if.
À utiliser : Pour faire du comportement polymorphe ou de la logique de sélection complexe basée sur les caractéristiques de l’objet.
string GetInfoForme(Forme f) => f switch
{
Cercle c => $"Cercle R={c.Rayon}",
Rectangle r => $"Rectangle L={r.Largeur} H={r.Hauteur}",
_ => "Inconnu"
};
GO TO FULL VERSION