Aller au contenu principal

enum et record

Notions théoriques

Les énumérations (enum)

Un enum (énumération) est un type qui définit un ensemble de constantes nommées. Il remplace des "nombres magiques" par des identifiants lisibles.

enum JourSemaine
{
Lundi, // = 0 par défaut
Mardi, // = 1
Mercredi, // = 2
Jeudi, // = 3
Vendredi, // = 4
Samedi, // = 5
Dimanche, // = 6
}

JourSemaine jour = JourSemaine.Mercredi;
Console.WriteLine(jour); // Mercredi
Console.WriteLine((int)jour); // 2

Valeurs explicites

On peut assigner des valeurs entières personnalisées :

enum CodeHTTP
{
OK = 200,
NotFound = 404,
ServerError = 500,
}

CodeHTTP reponse = CodeHTTP.NotFound;
Console.WriteLine((int)reponse); // 404

Conversion depuis un entier ou une chaîne

// Entier → enum
CodeHTTP code = (CodeHTTP)404;
Console.WriteLine(code); // NotFound

// Chaîne → enum (méthode sûre avec TryParse)
if (Enum.TryParse<CodeHTTP>("OK", out CodeHTTP resultat))
{
Console.WriteLine(resultat); // OK
}

[Flags] — enum combinables

L'attribut [Flags] permet de combiner des valeurs d'un enum via l'opérateur | (OU binaire). Les valeurs doivent être des puissances de 2.

[Flags]
enum Permissions
{
Aucune = 0,
Lire = 1,
Ecrire = 2,
Executer = 4,
Admin = Lire | Ecrire | Executer, // = 7
}

Permissions droits = Permissions.Lire | Permissions.Ecrire;
Console.WriteLine(droits); // Lire, Ecrire
Console.WriteLine(droits.HasFlag(Permissions.Lire)); // True
Console.WriteLine(droits.HasFlag(Permissions.Executer)); // False
info

HasFlag() est la méthode recommandée pour tester si un flag est actif dans une combinaison. Elle est plus lisible que les opérations binaires manuelles.

Les records (C# 9+)

Un record est un type spécialement conçu pour les données immuables. Il offre automatiquement :

  • Égalité par valeur (deux records avec les mêmes données sont égaux)
  • ToString() lisible (affiche toutes les propriétés)
  • Déconstruction automatique
  • Opérateur with pour créer une copie modifiée
// Syntaxe positionnelle (la plus courante)
record Produit(string Nom, decimal Prix, int Stock);

Produit p1 = new("Stylo", 1.50m, 100);
Produit p2 = new("Stylo", 1.50m, 100);

Console.WriteLine(p1); // Produit { Nom = Stylo, Prix = 1,50, Stock = 100 }
Console.WriteLine(p1 == p2); // True (égalité par valeur, pas par référence)

Opérateur with — copie immuable modifiée

Au lieu de modifier un record (ils sont immuables), on crée une nouvelle instance avec les propriétés souhaitées changées :

Produit p1 = new("Stylo", 1.50m, 100);
Produit p2 = p1 with { Prix = 1.80m }; // copie avec Prix modifié

Console.WriteLine(p1.Prix); // 1,50 (inchangé)
Console.WriteLine(p2.Prix); // 1,80

Déconstruction

Produit produit = new("Cahier", 2.50m, 50);
var (nom, prix, stock) = produit; // déconstruction automatique
Console.WriteLine($"{nom} coûte {prix:C}");

record struct (C# 10)

Un record struct est une version struct (type valeur) d'un record. Utile pour les petites structures de données fréquemment copiées (coordonnées, couleurs…).

record struct Point(double X, double Y);

Point a = new(1.0, 2.0);
Point b = a with { Y = 5.0 };

Usage typique : DTO (Data Transfer Object)

Les records sont idéaux pour les DTO : objets qui transportent des données entre les couches d'une application sans logique métier.

// DTO d'une réponse API
record UtilisateurDto(int Id, string Nom, string Email);

UtilisateurDto dto = new(42, "Alice", "alice@example.com");
attention

Un record est immuable par défaut (toutes les propriétés sont init). Si vous avez besoin de modifier une propriété après création, utilisez une classe classique avec propriétés set, ou déclarez la propriété mutablement dans le record.

Exemple pratique

using System;

// --- Enum : statut d'une livraison ---
enum StatutLivraison
{
EnAttente, // 0
Expedie, // 1
EnCours, // 2
Livre, // 3
Retourne, // 4
}

// --- Record : colis immuable ---
record Colis(
string Reference,
string Destinataire,
decimal PoidsKg,
StatutLivraison Statut
);

// --- Fonction de transition ---
Colis PasserAuStatutSuivant(Colis colis)
{
StatutLivraison prochain = colis.Statut switch
{
StatutLivraison.EnAttente => StatutLivraison.Expedie,
StatutLivraison.Expedie => StatutLivraison.EnCours,
StatutLivraison.EnCours => StatutLivraison.Livre,
_ => colis.Statut, // Livre ou Retourne : pas de changement
};

return colis with { Statut = prochain }; // nouveau record, l'original est inchangé
}

// --- Programme principal ---
Colis monColis = new("COL-2024-001", "Alice Dupont", 2.5m, StatutLivraison.EnAttente);
Console.WriteLine($"État initial : {monColis}");

monColis = PasserAuStatutSuivant(monColis);
Console.WriteLine($"Après expédition : {monColis.Statut}");

monColis = PasserAuStatutSuivant(monColis);
Console.WriteLine($"En transit : {monColis.Statut}");

monColis = PasserAuStatutSuivant(monColis);
Console.WriteLine($"Livré : {monColis.Statut}");

// Égalité par valeur
Colis copie = monColis with { }; // copie identique
Console.WriteLine($"\nOriginal == Copie : {monColis == copie}"); // True
info

with { } (accolades vides) crée une copie exacte du record. Vous pouvez ensuite y ajouter des overrides si besoin.

Test de mémorisation/compréhension


Quelle est la valeur entière par défaut du premier élément d'un `enum` non annoté ?


Quelle méthode vérifie si un flag est actif dans une valeur d'`enum [Flags]` ?


Que fait l'opérateur `with` sur un `record` ?


Deux `record` avec les mêmes valeurs sont-ils égaux avec `==` ?


Dans quel cas utilise-t-on `[Flags]` avec un `enum` ?


TP pour réfléchir et résoudre des problèmes

Vous allez créer un système de suivi de colis avec un enum StatutLivraison et un record Colis. Le programme simule les transitions de statut d'un colis.

Étape 1 — Déclarer l'enum StatutLivraison

Créez l'enum avec les statuts : EnAttente, Expedie, EnCours, Livre, Retourne.


Bonne pratique - Enum plutôt que chaînes ou entiers

Comparer statut == StatutLivraison.Livre est beaucoup plus sûr que statut == "livre" (sensible à la casse, aux fautes de frappe) ou statut == 3 (nombre magique illisible). L'enum offre la sécurité du typage et l'autocomplétion dans l'IDE.

Étape 2 — Déclarer le record Colis

Créez un record positionnel Colis avec les propriétés : Reference (string), Destinataire (string), PoidsKg (decimal), Statut (StatutLivraison).


Bonne pratique - Record pour les données sans comportement

Un colis est une donnée : référence, destinataire, poids, statut. Il n'a pas de comportement métier complexe. Le record est parfait ici : immuable, comparable par valeur, et son ToString() automatique est très pratique pour le débogage.

Étape 3 — Fonction de transition avec switch et with

Créez une fonction PasserAuStatutSuivant(Colis colis) qui retourne un nouveau colis avec le statut suivant dans la chaîne EnAttente → Expedie → EnCours → Livre.


Bonne pratique - Immuabilité et with

Modifier un record en place est impossible (ses propriétés sont init). C'est intentionnel : cela élimine les effets de bord. En retournant un nouveau record avec with, vous conservez l'historique de l'état précédent si vous en avez besoin, et vous évitez qu'une partie du code modifie un objet utilisé par une autre partie.

Étape 4 — Simuler la vie d'un colis

Créez un colis initial et simulez 3 transitions de statut en affichant l'état après chaque étape.


Bonne pratique - enum pour les états finis

Toutes les fois qu'une donnée ne peut prendre qu'un ensemble fini et connu de valeurs (statuts, directions, niveaux, types), utilisez un enum plutôt qu'une chaîne ou un entier. Vous bénéficiez de la vérification à la compilation, de l'autocomplétion, et d'un code beaucoup plus lisible.

📌 Une solution