CodeGym /Cours /C# SELF /record avec corps exp...

record avec corps explicite et record struct

C# SELF
Niveau 19 , Leçon 3
Disponible

1. Introduction

La syntaxe positionnelle d'une classe record est vraiment pratique pour les cas simples :

public record User(string Nom, int Âge);
Classe record positionnelle

Mais parfois, t'as grave envie d'ajouter des méthodes en plus à ton record, des propriétés custom, changer les modificateurs d'accès, ou mettre de la logique dans le constructeur (genre validation ou transformation automatique des données). Malheureusement, avec la syntaxe positionnelle, tu peux pas caser tout ça ! Il est temps de passer à la syntaxe explicite du corps si :

  • Tu veux étendre le record avec des méthodes, des propriétés ou de la logique supplémentaire.
  • Tu dois contrôler le comportement des propriétés (setter, getter, init, validation).
  • Tu veux créer plusieurs constructeurs différents pour différentes façons d'initialiser.
  • Tu dois ajouter des interfaces ou implémenter des méthodes spéciales.

Construire un record avec un corps

La syntaxe ressemble beaucoup à une classe. Tu connais sûrement cette forme :


public class User
{
    /* ... */
}

Mais maintenant, c'est un record :


public record User
{
    // Propriétés définies explicitement
    public string Nom { get; init; }
    public int Âge { get; init; }

    // Logique supplémentaire
    public string GetGreeting()
    {
        return $"Salut, je m'appelle {Nom} et j'ai {Âge} ans !";
    }

    // Constructeur custom
    public User(string nom, int âge)
    {
        Nom = nom;
        if (âge < 0)
            throw new ArgumentException("Âge ne peut pas être négatif !");
        Âge = âge;
    }
}

Fun fact : Si tu définis tes propriétés explicitement, le compilateur ne crée pas automatiquement les propriétés à partir de la syntaxe positionnelle. Tout est explicite, tout est sous contrôle.

Variante mixte : syntaxe hybride

C# te permet de mixer les deux mondes : tu peux déclarer un record positionnel et lui ajouter un corps :


public record User(string Nom, int Âge)
{
    public string GetGreeting()
    {
        return $"Je suis {Nom}, j'ai {Âge} ans !";
    }
}
Record positionnel avec corps et méthodes supplémentaires

Dans ce cas, les propriétés Nom et Âge sont toujours créées automatiquement grâce à la syntaxe positionnelle, et tes méthodes en plus sont bien au chaud dans le corps.

2. Différences avec les records classiques — subtilités du corps

  • Un record explicite te permet de tout contrôler : constructeurs, propriétés, méthodes.
  • Tu peux implémenter des interfaces ou ajouter ta propre logique de comparaison si tu utilises des règles d'identification d'objet compliquées.
  • Contrairement à un record simple avec syntaxe positionnelle, pour ajouter de nouvelles propriétés, il te suffit de les déclarer dans le corps du record.

// Erreur de débutant !
public record User(string Nom, int Âge)
{
    public string Nom { get; init; } // ← conflit ! Déclaration de propriété en double
}

Erreurs de débutant : Certains étudiants essaient d'écrire à la fois public record User(string Nom, int Âge) et d'ajouter dans le corps la propriété public string Nom { get; init; }, pensant que ce sont deux variables différentes. Nope ! Ça va faire un conflit (déclaration en double). Soit tu utilises la syntaxe positionnelle à fond, soit tu déclares explicitement les propriétés — ne mélange pas.

Exemples pratiques

On continue à développer une appli console où l'utilisateur crée des commandes. Disons qu'on a une classe commande Order qui ressemblait à ça jusqu'ici :

public record Order(string Produit, int Quantité, double Prix);

Supposons que maintenant on a besoin de valider la quantité (quantité ne peut pas être inférieure à 1) et d'ajouter une propriété compteur pour le coût total :


public record Order
{
    public string Produit { get; init; }
    public int Quantité { get; init; }
    public double Prix { get; init; }
    public double CoûtTotal => Quantité * Prix;

    public Order(string produit, int quantité, double prix)
    {
        Produit = produit ?? throw new ArgumentNullException(nameof(produit));
        if (quantité < 1)
            throw new ArgumentException("La quantité doit être au moins 1 !");
        Quantité = quantité;
        Prix = prix;
    }

    public override string ToString()
        => $"Produit : {Produit}, Qté : {Quantité}, Total : {CoûtTotal}";
}

Remarque qu'on a défini explicitement les propriétés, on les a mises en init-setter (objets immuables), on a ajouté le calcul automatique du coût — le code est devenu plus flexible !

Appel dans le programme :

var order = new Order("Vélo", 2, 15000);
Console.WriteLine(order); // Produit : Vélo, Qté : 2, Total : 30000

3. record struct

Évolution des struct

Avant l'arrivée du record struct, les structures en C# étaient des "petits bourrins" — copiées vite fait, stockées sur la stack, parfaites pour des petits “parcels” de données (genre coordonnées ou couleurs). Mais elles n'avaient pas tous les goodies des records : pas de syntaxe positionnelle, pas de with-expressions, pas de comparaison par valeur par défaut, ni toutes ces “petites douceurs”.

Maintenant, en C#, tu peux déclarer des structs façon record :

public record struct Point(int X, int Y);
Syntaxe positionnelle record struct

Mais ça sert à quoi tout ça ?

  • Implémentation automatique de Equals, GetHashCode, ToString — ta struct sait se comparer et s'afficher joliment !
  • Syntaxe with-clonage : var p2 = p1 with { X = 10 };
  • Tu peux utiliser la syntaxe positionnelle ou explicite.

Comparaison : struct classique VS record struct

struct record struct
Syntaxe positionnelle Non Oui
with-expression Non Oui
Immuabilité Non (par défaut) Oui (init)
Comparaison par valeur Non (par défaut) Oui
ToString Standard Joli

Syntaxe explicite du corps pour record struct

C'est pareil que pour un record classique, mais avec struct :


public record struct Rectangle
{
    public int Largeur { get; init; }
    public int Hauteur { get; init; }

    public int Aire => Largeur * Hauteur;

    public Rectangle(int largeur, int hauteur)
    {
        Largeur = largeur > 0 ? largeur : throw new ArgumentException("Largeur > 0");
        Hauteur = hauteur > 0 ? hauteur : throw new ArgumentException("Hauteur > 0");
    }

    public void Print()
    {
        Console.WriteLine($"Dimensions : {Largeur} x {Hauteur}, aire : {Aire}");
    }
}

Exemple d'utilisation :

var rect = new Rectangle(10, 7);
rect.Print(); // Dimensions : 10 x 7, aire : 70

// On clone et on change la largeur, sans toucher à l'original
var wideRect = rect with { Largeur = 20 };
wideRect.Print(); // Dimensions : 20 x 7, aire : 140

Particularités du record struct

  • Ça reste un struct — value type. Ça se copie à l'affectation !
  • Tous les avantages des records : comparaison par valeur, clonage with, joli ToString.
  • À utiliser pour des petits ensembles de données immuables et compacts, quand tu veux éviter les allocations sur le heap.
  • Tu peux déclarer des paramètres positionnels ou “déplier” le corps explicitement.

4. Erreurs typiques et pièges

Trop de mutabilité : record struct ne rend pas les champs automatiquement immuables si tu les déclares comme normaux (genre public int Valeur;). Utilise les init-setters pour un struct vraiment immutable !

Comparaison : Si tu ajoutes des champs à la main (sans les mettre dans la syntaxe positionnelle), sache que seuls les champs dans le constructeur ou avec init-setter participent à la comparaison automatique par valeur.

Copie : C'est un struct, donc... tout est copié ! Ne confonds pas avec les records par référence.

Confusion avec les with-expressions : Elles font toujours une shallow copy, donc PAS de copie profonde des objets imbriqués.

Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION