1. Introduction
La syntaxe positionnelle d'une classe record est vraiment pratique pour les cas simples :
public record User(string Nom, int Âge);
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 !";
}
}
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);
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.
GO TO FULL VERSION