Accéder à l'en-tête Accéder au contenu principal Accéder au pied de page
Retour aux actualités
Bonnes pratiques de dév Craft DDD Object
15/04/2021 Dorra BARTAGUIZ

Dépoussierons les structures en c#

Dépoussiérons les structures

Face à une nouvelle fonctionnalité, on cherche souvent à modéliser nos concepts en utilisant les classes. Et pourtant, nous avons en csharp un type qui s’appelle « struct » (pour structure) qui existe et auquel on pense rarement voire jamais. C’est peut être naturel de partir sur les classes puisqu’on fait de la programmation orientée objet, mais les questions qui peuvent se poser sont :

A-t-on besoin de ce type struct quelque part ou finalement les classes suffisent-elles amplement ?

Quelle est la différence entre une structure et une classe en C# ?

Définition

D’après la documentation de Microsoft, les classes et les structures sont des types de base en .Net. Tous deux sont des unités logiques qui encapsulent des données et des comportements.

La classe est un type référence donc quand on crée une instance d’une classe on alloue réellement de la mémoire. Quand l’instance est assignée à une variable, cette variable pointe sur l’adresse mémoire allouée pour l’objet. Et si deux variables pointent sur la même référence, si l’on change des données d’une variable la deuxième est modifiée aussi puisque derrière il s’agit de la même donnée.

La structure est type valeur donc la variable à laquelle on assigne une nouvelle structure, contient les données réelles de la structure (ce n’est pas une simple référence à cette structure). Si on crée une nouvelle variable à laquelle on assigne la même structure, c’est une copie de toutes les valeurs qui est faite et non pas une référence comme c’est le cas d’une instance de classe. Ainsi si on change les valeurs de la première variable, il n’y a aucune incidence sur la deuxième variable.

Il faut savoir aussi qu’un type valeur a toujours une valeur contrairement à une référence qui peut être null (elle ne référence aucune donnée). Le type valeur est initialisé dans la pile (stack) alors que le type référence est créé dans la pile mais référence des cases mémoire dans le tas (heap).

Cet article décrit bien la différence entre les types valeur et référence The stack is an implementation detail part one.

Cas d’utilisation

Dans nos applications, on peut avoir plusieurs cas de figures à gérer. On va les lister ici et pour chaque cas, nous verrons quel type est plus adapté. Dans une approche DDD (Domain Driven Design : DDD en 5 minutes), nous pouvons avoir des patterns tactiques qui apparaitront sous forme de données dans notre application.

Les value-objects

On a besoin parfois de représenter un concept immuable dans notre application comme les devises par exemple. La devise « EUR » est toujours la même dans le contexte de notre application par exemple.

On peut représenter la devise par une classe « Currency » qui contient un libellé de la devise en tant qu’attribut de la classe. Comme la devise est immuable, on ne va pas exposer un setter pour l’attribut et on va redéfinir les méthodes Equals et HashCode.

Par contre si on représente la devise comme une structure, on n’a pas besoin de redéfinir Equals et GetHashCode puisque la comparaison entre structures se base nativement sur l’égalité de tous les attributs de la structure. Donc finalement on gagne du temps d’implémentation en utilisant la structure et on gagne aussi en terme de tests à écrire et de couverture de code 😉

Value-object = Struct

Entity

C’est un concept qui a une identité et un état donc un cycle de vie. Ce concept est par conséquent mutable. L’égalité entre deux entités se base uniquement sur l’identité. On n’a pas besoin donc de vérifier les autres attributs. Tout de suite l’utilisation du type structure est moins pertinente.

Entity = Class

Aggregate

Un aggregat représente un ensemble de données ne pouvant pas être traitées séparément. Il peut regrouper Entity et Value-object. Cet ensemble n’est pas dissociable et il ne doit être mis à jour qu’à travers la racine de l’objet.

Ainsi la structure n’est pas adaptée à ce type de représentation d’objets.

Aggregate = Class

Monoïd

Un monoïde est une structure algébrique. Il représente un ensemble d’éléments avec des propriétés et des opérations. La composition de deux éléments du même ensemble donne un troisième élément du même ensemble. Si vous souhaitez en apprendre plus sur les monoïdes, je vous invite à visualiser ce talk de Cyrille Martraire.

Un monoïde est représenté généralement par un value-object. Donc on peut représenter un monoïde par une structure.

Monoïd = Struct

Mais attention

Dans le paragraphe précédent on a vu les cas d’utilisation des structures. Maintenant, je vous propose de voir lorsque je vous déconseille de les utiliser.

Beaucoup d’attributs

Il est déconseillé d’utiliser des structures avec beaucoup d’attributs. Dans le cas où vous considérez que c’est judicieux d’utiliser le type Struct mais que vous avez une dizaine d’attributs, je vous conseille de découper votre structure en plusieurs petites pour optimiser les performances.

En DTO

Les cas d’utilisation montrés dans le paragraphe précédent illustrent bien le fait d’utiliser la structure pour les concepts au sein d’un domaine. Il vaut mieux éviter l’utilisation du type Struct dans un DTO (Data Transfer Object) par exemple ou dans la couche infrastructure. Ainsi on évite les problématiques liées au boxing/unboxing.

Avant de finir

L’utilisation des structures pour les value-objects et les monoïdes, requière l’application de l’immutabilité. Comme on le sait le langage C# permet de rompre l’immutabilité via l’exposition des attributs (avec les propriétés ou avec des méthodes). On peut donc être tenté·e de rajouter des setter pour les attributs de la structure, mais il faut se rappeler qu’:

  • exposer les attributs aux modifications (utiliser un set) casse la notion d’immutabilité.
  • Et exposer les attributs en lecture (utiliser un get) va à l’encontre du principe de « Tell don’t ask« 

Il ne faut pas oublier le piège dans lequel on peut facilement tomber lors de l’utilisation des collections. En effet, exposer une collection de structures ou exposer une collection dans une structure permet de modifier ses éléments et donc rompre l’immutabilité.

Conclusion

Maintenant que vous connaissez la différence entre une classe et une structure, amusez-vous bien en jonglant entre les deux types. Et pour les fans des nouveautés du langage, dans la version 9 du langage C# on retrouve les Records qui sont des types référence.