C# : Les records
Immutabilité, comparaison par valeur, with-expressions - les records pour des données propres en C#.
C# : Les records
Introduits en C# 9, les records sont des types conçus pour représenter des données immuables. Ils ressemblent aux classes mais avec des comportements intégrés : comparaison par valeur, ToString() automatique, copie simplifiée.
# Pourquoi les records ?
Avec une classe classique, deux objets avec les mêmes données ne sont pas égaux par défaut - C# compare les références, pas les valeurs :
class Personne
{
public string Nom { get; init; }
public int Age { get; init; }
}
Personne a = new Personne { Nom = "Alice", Age = 30 };
Personne b = new Personne { Nom = "Alice", Age = 30 };
Console.WriteLine(a == b); // false ! Ce sont deux objets différentsclass Personne{ public string Nom { get; init; } public int Age { get; init; }} Personne a = new Personne { Nom = "Alice", Age = 30 };Personne b = new Personne { Nom = "Alice", Age = 30 }; Console.WriteLine(a == b); // false ! Ce sont deux objets différentsLes records résolvent ça. Deux records avec les mêmes valeurs sont égaux :
record Personne(string Nom, int Age);
Personne a = new Personne("Alice", 30);
Personne b = new Personne("Alice", 30);
Console.WriteLine(a == b); // true !record Personne(string Nom, int Age); Personne a = new Personne("Alice", 30);Personne b = new Personne("Alice", 30); Console.WriteLine(a == b); // true !# Syntaxe
Syntaxe positionnelle (la plus courante)
record Personne(string Nom, int Age);record Personne(string Nom, int Age);Une seule ligne. Le compilateur génère automatiquement :
- Les propriétés
NometAgeen lecture seule (init) - Un constructeur avec les deux paramètres
Equals()et==qui comparent par valeurGetHashCode()cohérent avec l'égalitéToString()lisible :Personne { Nom = Alice, Age = 30 }- La méthode
Deconstruct()pour le pattern matching
Syntaxe complète
Si tu as besoin de plus de contrôle :
record Personne
{
public string Nom { get; init; }
public int Age { get; init; }
public string Email { get; init; }
// Propriété calculée
public bool EstMajeur => Age >= 18;
}record Personne{ public string Nom { get; init; } public int Age { get; init; } public string Email { get; init; } // Propriété calculée public bool EstMajeur => Age >= 18;}# Copie avec modification (with)
Les records sont immuables - on ne modifie pas un record, on en crée un nouveau avec les changements souhaités :
record Personne(string Nom, int Age);
Personne alice = new Personne("Alice", 30);
Personne aliceAgee = alice with { Age = 31 };
Console.WriteLine(alice); // Personne { Nom = Alice, Age = 30 }
Console.WriteLine(aliceAgee); // Personne { Nom = Alice, Age = 31 }record Personne(string Nom, int Age); Personne alice = new Personne("Alice", 30);Personne aliceAgee = alice with { Age = 31 }; Console.WriteLine(alice); // Personne { Nom = Alice, Age = 30 }Console.WriteLine(aliceAgee); // Personne { Nom = Alice, Age = 31 }with copie toutes les propriétés et ne change que celles spécifiées. L'original n'est jamais modifié.
# Record class vs record struct
Par défaut, un record est un type référence (comme une classe). Depuis C# 10, on peut aussi créer des record structs :
// Record class (défaut) - type référence, heap
record Personne(string Nom, int Age);
// Record struct - type valeur, stack
record struct Point(double X, double Y);// Record class (défaut) - type référence, heaprecord Personne(string Nom, int Age); // Record struct - type valeur, stackrecord struct Point(double X, double Y);record (class) | record struct | readonly record struct | |
|---|---|---|---|
| Stockage | Heap | Stack | Stack |
| Copie | Par référence | Par valeur | Par valeur |
| Propriétés | get/init (immuables) | get/set (mutables) | get (immuables) |
with | Oui | Oui | Oui |
| Égalité par valeur | Oui | Oui | Oui |
Attention : un record struct a des propriétés mutables par défaut (get/set). Si tu veux l'immutabilité, utilise readonly record struct.
# Héritage de records
Les records supportent l'héritage (uniquement record -> record) :
record Personne(string Nom, int Age);
record Etudiant(string Nom, int Age, string Universite) : Personne(Nom, Age);
Etudiant e = new Etudiant("Alice", 20, "Paris-Saclay");
Console.WriteLine(e); // Etudiant { Nom = Alice, Age = 20, Universite = Paris-Saclay }record Personne(string Nom, int Age);record Etudiant(string Nom, int Age, string Universite) : Personne(Nom, Age); Etudiant e = new Etudiant("Alice", 20, "Paris-Saclay");Console.WriteLine(e); // Etudiant { Nom = Alice, Age = 20, Universite = Paris-Saclay }L'égalité prend en compte le type : un Personne et un Etudiant avec les mêmes Nom et Age ne sont pas égaux.
# Quand utiliser un record
Les records sont le bon choix pour :
- Les DTO (Data Transfer Objects) - données échangées entre couches
- Les événements -
record OrderPlaced(Guid OrderId, DateTime At) - Les clés composites - l'égalité par valeur évite les bugs
- Tout objet qui représente une donnée plutôt qu'une entité avec comportement
La règle simple : si l'objet est défini par ce qu'il contient (ses données) plutôt que par ce qu'il est (son identité), c'est un record.
# La suite
- Les enums - énumérations et constantes nommées