CodeGym /Cours /C# SELF /Différences entre record, class et struct

Différences entre record, class et struct

C# SELF
Niveau 19 , Leçon 4
Disponible

1. Pourquoi on avait besoin des record ?

En C# (et .NET en général), les blocs de base étaient longtemps les classes (class) et les structures (struct). Mais chacun a ses limites pour certains cas. Les classes — c’est des types référence, mutables, et la comparaison se fait par référence (sauf exceptions rares). Les structs — c’est des types valeur (copiés à la transmission), comparés par défaut octet par octet et souvent mutables (jusqu’à l’apparition des readonly struct).

Mais si tu voulais juste une façon simple et naturelle de stocker des données, facile à comparer par valeur, à cloner vite fait et sans te prendre la tête sur qui a modifié ton objet — fallait bricoler des trucs ou utiliser des libs comme ValueTuple ou même System.Tuple. Mais c’est jamais aussi clean et expressif qu’on voudrait.

C’est pour ça qu’en C# 9, ils ont sorti record — un type de données qui combine déclaration courte, sécurité de l’immutabilité et comportement orienté comparaison par valeur.

2. Les quatre types sur une seule page

class struct record record struct
Catégorie Type référence Type valeur Type référence Type valeur
Mutabilité Par défaut — oui Par défaut — oui Par défaut — non (init) Par défaut — non (init)
Comparaison Par référence (==) Par valeur (champs) Par valeur (fields/properties) Par valeur (fields)
Clonage Manuellement seulement Manuellement seulement Support intégré (with) Support intégré (with)
Héritage Oui Non Oui Non
Immutabilité À implémenter À implémenter Super simple à faire Super simple à faire
Syntaxe La plus longue Court La plus courte (positionnel) Assez court
Utilisation en collections Par référence Copies Par référence Copies

Schéma visuel

+----------------+     +----------------+     +--------------------+
|    class       |     |    struct      |     |      record        |
+----------------+     +----------------+     +--------------------+
| Reference Type |     | Value Type     |     | Reference Type     |
| Mutable        |     | Mutable        |     | Immutable (init)   |
| == : Reference |     | == : By Fields |     | == : By Value      |
+----------------+     +----------------+     +--------------------+

3. L’essence des différences entre record, class et struct

Comportement en mémoire : référence ou valeur ?

  • class et record — ce sont des types référence. Quand tu les passes à une fonction, c’est la référence à l’objet qui est copiée.
  • struct et record struct — types valeur. Toujours copiés octet par octet (sauf si tu les passes explicitement par référence).

class PointClass { public int X; public int Y; }
struct PointStruct { public int X; public int Y; }
record PointRecord(int X, int Y);
record struct PointRecordStruct(int X, int Y);

void Demo()
{
    var pc = new PointClass { X = 1, Y = 2 };
    var ps = new PointStruct { X = 1, Y = 2 };
    var pr = new PointRecord(1, 2);
    var prs = new PointRecordStruct(1, 2);

    ChangeY(pc);    // pc.Y va changer !
    ChangeY(ps);    // ps.Y ne changera pas — copie !
    ChangeY(pr);    // pr.Y va changer !
    ChangeY(prs);   // prs.Y ne changera pas — copie !
}

void ChangeY(dynamic p) { p.Y = 99; }

Si tu trouves que dynamic ici c’est de la magie — ouais, c’est juste pour l’exemple, pour que tu voies qu’avec les structs la valeur ne change pas, alors qu’avec une class ou un record (référence) — ça change.

Comparaison : comment savoir si deux objets sont égaux ?

  • class : comparé par référence (== — true seulement si c’est le même objet en mémoire), sauf si tu overrides Equals.
  • struct : comparé par valeur de tous les champs (par défaut).
  • record : comparé par valeur de tous les champs/propriétés définis dans le constructeur principal.
class Foo { public int A; public int B; }
record Bar(int A, int B);

var foo1 = new Foo { A = 42, B = 1 };
var foo2 = new Foo { A = 42, B = 1 };
var bar1 = new Bar(42, 1);
var bar2 = new Bar(42, 1);

Console.WriteLine(foo1 == foo2); // False ! Objets différents
Console.WriteLine(bar1 == bar2); // True ! Valeurs identiques

Fun fact :
Avec record struct c’est encore plus cool : la comparaison est "par valeur", comme un struct classique, mais avec la syntaxe et les features des records.

4. Immutabilité : qui garantit quoi ?

Comparons la sécurité des objets :

  • class : Par défaut, facilement modifiable, sauf si tu mets tous les champs en readonly.
  • struct : Pareil, mais tu peux déclarer readonly struct, alors ni les champs ni les propriétés ne peuvent changer.
  • record : D’habitude, les champs sont déclarés avec le modificateur init, donc tu peux les définir que dans le constructeur ou l’initialiseur d’objet (with). Pratique pour transmettre des données en toute sécurité.
  • record struct : Pareil, tu peux faire readonly record struct et avoir l’immutabilité des types valeur avec les bonus des records.

record Person(string Name, int Age);

var p1 = new Person("Alexey", 23);
// p1.Age = 24; // Erreur ! Seulement init

var p2 = p1 with { Age = 24 }; // Ça marche ! Crée une copie avec de nouvelles données

Quand tu bosses sur un gros projet avec des dizaines d’entités, record t’évite plein de bugs du genre "quelqu’un a changé un champ quelque part — et tout a pété".

5. Syntaxe : à quoi ressemblent les déclarations et comment ne pas se perdre


// class
public class Product
{
    public int Id { get; init; }
    public string Name { get; init; }
}

// struct
public struct Point
{
    public int X { get; set; }
    public int Y { get; set; }
}

// record
public record Product(int Id, string Name);

// record struct
public record struct Point(int X, int Y);
Comparaison de la syntaxe de déclaration des différents types

Comme tu vois, la déclaration la plus courte c’est pour le type record. Tu as le constructeur, le destructeur, l’égalité, ToString() et plein d’autres trucs "out of the box".

Syntaxe Ce que tu as gratos Héritage possible ?
class rien (ou presque) Oui
struct rien (ou presque) Non
record ToString, Equals, Deconstruct, with Oui
record struct ToString, Equals, Deconstruct, with Non

6. Clonage et opérateur with

Seuls record et record struct ont l’opérateur spécial with, qui te permet de copier un objet en changeant juste certaines propriétés, sans prise de tête.


record User(string Name, int Age);

var user1 = new User("Irina", 28);
var user2 = user1 with { Age = 29 }; // user2.Name == "Irina", user2.Age == 29

Avec une classe (class), tu dois écrire la méthode de copie à la main, et si tu oublies de copier un champ — bonjour les bugs.

7. Héritage : qui hérite de qui

  • class : Supporte l’héritage classique (hiérarchies de classes, méthodes virtuelles, abstractions, etc.).
  • struct : Ne supporte pas (peut juste implémenter des interfaces).
  • record : Supporte l’héritage, mais avec quelques limites (par exemple, héritage possible seulement entre records, pas avec class).
  • record struct : Ne supporte pas, comme les structs classiques.
record Animal(string Name);
record Dog(string Name, string Breed) : Animal(Name); // Ok !

class Vehicle { }
class Car : Vehicle { } // Ok !

// struct ne peut pas hériter d’un struct

Plus sur l’héritage au prochain niveau :P

8. Où utiliser quel type en pratique

  • class : Gros objets avec beaucoup de logique, longue durée de vie, état mutable, hiérarchie (genre logique métier, composants UI).
  • struct : Petits objets-valeurs, où la copie rapide est importante, pas de GC et overhead minimal (genre coordonnées, couleurs, montants — tout ce qui se clone vite et sans douleur).
  • record : DTO (Data Transfer Object), objets-paramètres, paramètres de config, états immuables, résultats de calculs, qu’on veut comparer facilement par contenu.
  • record struct : Types valeur où tu veux de l’immutabilité et le comportement des records, mais sans allocations inutiles sur le heap.

9. Erreurs classiques et pièges

Parfois, les devs pensent que record c’est juste "un remplaçant de class". Faux ! Si tu fais un objet qui doit changer d’état au cours de sa vie — utilise class.
Si tu veux comparer les objets par référence (genre dans un pattern singleton ou quand le cycle de vie est critique) — utilise class.
Si tu fais un objet-valeur qui doit se comporter comme un nombre ou un point sur une grille — struct ou record struct.
Si tu bosses avec des objets immuables, faciles à comparer, souvent transmis entre couches, stockés en collections, logués et sérialisés — choisis record.

Pense aussi à ça : si tu déclares un record-class avec des propriétés en lecture seule, mais que tu oublies les objets imbriqués — les champs imbriqués peuvent quand même être modifiés s’ils sont eux-mêmes mutables.


record Student(string Name, List<int> Grades);

var s1 = new Student("Anton", new List<int>() {5,5,5});
var s2 = s1 with { };

s2.Grades.Add(2); // Les deux objets sont "maudits" par le 2 ! s1.Grades == s2.Grades
2
Mission
C# SELF, niveau 19, leçon 4
Bloqué
Création d'un record et comparaison d'objets
Création d'un record et comparaison d'objets
2
Mission
C# SELF, niveau 19, leçon 4
Bloqué
Utilisation de l'opérateur `with` pour enregistrer des données
Utilisation de l'opérateur `with` pour enregistrer des données
1
Étude/Quiz
Problème avec DTO, niveau 19, leçon 4
Indisponible
Problème avec DTO
DTO, record et with
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION