CodeGym /Cours /C# SELF /Découverte de record ...

Découverte de record et de la syntaxe positionnelle

C# SELF
Niveau 19 , Leçon 1
Disponible

1. Le problème du transfert de données

Imaginons qu'on a une appli pour une école, et qu'on doit transmettre des infos sur un élève entre différents modules : nom, année de naissance, classe. Comment on fait d'habitude ?

On crée une classe du genre :


public class Student
{
    public string Name { get; set; }
    public int YearOfBirth { get; set; }
    public string Class { get; set; }
}
Classe pour stocker les données d'un élève (ancienne méthode)

Ça a l'air classique. Mais cette approche a quelques soucis :

  • Pour comparer deux élèves, par défaut c'est juste la référence de l'objet qui est comparée. Donc deux élèves avec les mêmes champs mais des objets différents — ne sont pas égaux !
  • La classe peut être modifiée après création, ce qui peut causer des bugs (surtout si l'objet est déjà utilisé quelque part) ;
  • Beaucoup de code « boilerplate » : constructeurs, méthodes de comparaison, de copie (clones), ToString.

Et comme tu t'en doutes sûrement : C# peut nous éviter tout ça ! Voici — record.

2. C'est quoi un record ?

Un record — c'est un type spécial en C# conçu spécialement pour stocker des données. Un record a deux gros avantages :

  1. Immutabilité (immutability) : les objets de type record sont immuables par défaut, c'est-à-dire que leurs propriétés sont fixées à la création et ne changent plus (en vrai, les setters sont privés). Même si tu peux déclarer des records mutables, par défaut — c'est immuable.
  2. Comparaison par valeur : si deux objets record ont les mêmes valeurs dans tous leurs champs, ils sont considérés comme égaux (== et .Equals() fonctionnent différemment !).

En vrai, un record — c'est l'emballage parfait pour transférer des données entre les couches d'une appli (genre, de la BDD au contrôleur, du contrôleur à la vue, etc.).

3. Syntaxe des records

Le moyen le plus simple — la syntaxe positionnelle

Quand on veut juste transmettre un ensemble de valeurs, on déclare le type avec une seule ligne :


public record Student(string Name, int YearOfBirth, string Class);
Syntaxe positionnelle d'un record

Qu'est-ce qui se passe sous le capot ? Le compilateur va générer pour nous :

  • Des propriétés automatiques en lecture seule (avec un setter privé) ;
  • Un constructeur qui prend tous les paramètres ;
  • Des méthodes de comparaison et de copie ;
  • Un ToString stylé qui formate bien l'affichage !

Utilisation d'un record positionnel

Essayons d'utiliser ce nouveau type dans notre appli scolaire :

var student1 = new Student("Ivan", 2008, "8A");
var student2 = new Student("Maria", 2008, "8B");

L'accès aux propriétés se fait comme d'hab (sauf qu'on ne peut pas les modifier) :

Console.WriteLine($"{student1.Name}, {student1.YearOfBirth}, {student1.Class}");

Tenter de modifier une propriété après création

student1.Name = "Petr"; // Erreur ! Propriété en lecture seule.

Si tu décommentes cette ligne — le compilateur va râler direct : impossible d'assigner une valeur à une propriété en lecture seule.

Voilà à quoi ressemble le ToString généré automatiquement

Console.WriteLine(student1); // Affichera : Student { Name = Ivan, YearOfBirth = 2008, Class = 8A }

C'est propre et clair même sans formatage manuel !

4. Comparaison des objets record

Petit rappel : si tu crées deux objets différents d'une classe classique avec les mêmes données, ils ne seront quand même pas égaux :

var a = new Student("Ivan", 2008, "8A");
var b = new Student("Ivan", 2008, "8A");

Console.WriteLine(a == b); // Pour une classe : false

Mais si Student est un record, alors l'égalité marche comme tu t'y attends :


public record Student(string Name, int YearOfBirth, string Class);

var a = new Student("Ivan", 2008, "8A");
var b = new Student("Ivan", 2008, "8A");

Console.WriteLine(a == b); // Pour un record : true !
Comparaison des records par valeur

Donc deux records avec les mêmes champs sont considérés égaux même si ce sont deux objets différents en mémoire.

5. À quoi ça ressemble à l'intérieur

Les étudiants sont souvent surpris de voir tout ce que le compilateur fait pour nous avec les records. Comparons pour voir ce qu'il faudrait écrire à la main pour une classe, et ce que fait un record.

La bonne vieille classe écrite à la main

public class Student
{
    public string Name { get; }
    public int YearOfBirth { get; }
    public string Class { get; }

    public Student(string name, int yearOfBirth, string @class)
    {
        Name = name;
        YearOfBirth = yearOfBirth;
        Class = @class; //référence à sa propre classe
    }

    public override bool Equals(object? obj)
    {
        if (obj is not Student other) return false;
        return Name == other.Name && YearOfBirth == other.YearOfBirth && Class == other.Class;
    }

    public override int GetHashCode()
    {
        return HashCode.Combine(Name, YearOfBirth, Class);
    }

    public override string ToString()
    {
        return $"Student {{ Name = {Name}, YearOfBirth = {YearOfBirth}, Class = {Class} }}";
    }
}

Pas étonnant que les devs deviennent parano — on écrit dix fois la même chose !

Record — une seule ligne


public record Student(string Name, int YearOfBirth, string Class);
Record — tout pareil, mais en une ligne !

6. Record et immutabilité : ce qu'on peut faire ou pas

Les records ont par défaut des propriétés en lecture seule, et ça évite plein de bugs. Mais si tu veux vraiment (genre, tu bosses avec un vieux API), tu peux déclarer des records mutables :

public record MutableStudent
{
    public string Name { get; set; }
    public int YearOfBirth { get; set; }
    public string Class { get; set; }
}

Maintenant tu peux modifier ces champs, mais tu perds certains avantages (la sécurité, par exemple).

7. Déstructuration d'un record

Comme la syntaxe positionnelle ressemble beaucoup aux tuples, tu peux facilement déstructurer un record :


var student = new Student("Ivan", 2008, "8A");

var (name, year, className) = student;

Console.WriteLine($"{name} - {year}, {className}"); // Ivan - 2008, 8A
Déstructuration d'un record positionnel

Le compilateur génère une méthode Deconstruct pour chaque record positionnel, et ça rend la vie plus cool avec LINQ, les switch-patterns et tout le reste.

8. Record — c'est une classe ou un struct ?

Par défaut, un record — c'est un type référence, comme une classe. Donc tout le comportement des types référence (stockage sur le heap, copie de référence, etc.) marche normalement.

Si tu veux un type valeur (value type), C# a aussi ce qu'il faut — tu peux écrire :


public record struct Point(int X, int Y);
Record struct — record de type valeur

Mais pour transférer des données, on utilise presque toujours la forme classique des records, donc type référence. Plus de détails sur record struct — dans les prochaines leçons :P

Comparaison entre class, struct, record

Type Immutabilité par défaut Comparaison par valeur Déstructuration facile Auto ToString
class Non Non (par référence) Non Non
struct Non Oui Non Non
record Oui Oui Oui Oui

9. Particularités et erreurs fréquentes

Beaucoup de débutants se mélangent dans les subtilités des records. Par exemple, certains pensent que modifier un champ d'un record va modifier l'autre (comme avec les classes et la copie de référence). Non ! Aussi, quand tu utilises with, rappelle-toi qu'une copie est toujours créée, l'original ne change pas. Les records sont parfaits pour la logique où ton appli veut garder des données propres et prévisibles.

Oui, si tu déclares des champs avec init au lieu de set, tu peux aussi les assigner seulement à la création ou avec with, mais pas après.

2
Mission
C# SELF, niveau 19, leçon 1
Bloqué
Comparaison de record par valeur
Comparaison de record par valeur
2
Mission
C# SELF, niveau 19, leçon 1
Bloqué
Déstructuration d'un record
Déstructuration d'un record
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION