Les classes et records
Introduction
Les classes et les records sont deux types "référence". Tous deux permettent de créer des objets, mais les objets obtenus n'ont pas le même comportement et ne jouent pas le même rôle au sein d'un logiciel.
Les classes
Une classe est un type permettant de créer des objets. Ces objets ont des membres, tels que des attributs, des propriétés, des méthodes, des constructeurs, des évènements, etc. Voici un exemple :
public class Customer
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
}
Lorsque nous instancions cette classe :
var customer1 = new Customer {Id = 1, Name = "James"};
var customer2 = new Customer {Id = 2, Name = "Laurence"};
L'usage des classes est approprié pour créer des objets métiers ou techniques, ayant un cycle de vie. Durant leur existence, ils vont proposer des services utilisant les données contenues dans leur état. Et si dans l'implémentation d'un ces services, nous modifions l'état d'un objet :
Les objets créés sont mutables. C'est pourquoi, nous modifions l'état de l'objet existant.
Si nous comparons les objets customer1 et customer2 :
Alors isSameCustomer vaut false, car customer1 et customer2 sont deux instances différentes de la classe Customer.
Les records
Les records sont conçus pour faciliter l’immutabilité. Voici la déclaration d'un record :
Voici une autre implémentation, simplifiée avec l'utilisation des constructeurs primaires :
Avec ce record, nous pouvons créer des objets :
Par défaut, les records sont conçus pour favoriser l'immuabilité. Le compilateur génère généralement des propriétés avec un setter d'initialisation (init), qui ne peuvent être valorisées uniquement lors de la création de l'objet. Autrement dit, une fois qu'un objet a été créé, son état n'est normalement plus modifié. Toutefois, si nous modifions l'état d'un objet de la manière suivante (en remplaçant le modificateur init par set dans la propriété Name) :
Alors l'état de l'objet customer2 est modifié. En revanche, si nous le modifions de la manière suivante :
Alors la propriété Name de l'objet customer2 n'est pas modifiée (elle vaut toujours "James"), car cet objet est immuable. Seul l'objet customer2Bis a une propriété Name qui vaut "Baptiste" (sa propriété Id valant 2). L'opérateur with ne modifie pas l'objet existant. Il crée une nouvelle instance à partir de l'objet initial en ne changeant que les propriétés indiquées.
Je dis souvent cette phrase pour distinguer leur comportement :
Si nous comparons les objets customer1 et customer2 :
Alors isSameCustomer vaut true, si les valeurs des propriétés des objets customer1 et customer2 sont identiques. Les records réduisent le code à écrire. Le compilateur génère automatiquement plusieurs fonctionnalités : l'égalité par valeur (Equals, ==), GetHashCode(), ToString(), ainsi que le support de l'opérateur with.
Quelles sont les plus-values de cette immutabilité proposée par les records ?
Les principaux avantages de cette immutabilité sont les suivants :
-
L'état des objets est maîtrisé
-
Sécurité dans les traitements multi-threads. Comme un objet immutable ne change jamais d'état après sa création, plusieurs threads peuvent le partager simultanément sans synchronisation particulière
-
Dans les applications Blazor WebAssembly, l'immutabilité joue un rôle important. Des bibliothèques comme Fluxor, qui implémentent le pattern Redux, reposent sur le remplacement des états plutôt que sur leur modification. Les records sont donc particulièrement adaptés car ils facilitent la création de nouveaux états, et permettent une détection efficace des changements, améliorant ainsi les performances du rendu des composants
-
Les records sont pratiques pour implémenter des DTO (Data Transfer Object), ou des commandes CQRS (services distinguant la lecture et la mise à jour des données)