Blog Arolla

Dépoussiérons 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.

Plus de publications

2 comments for “Dépoussiérons les structures en C#

  1. Debove Christophe
    16 avril 2021 at 19 h 56 min

    Bonjour,

    Super article, il est clair que les struct sont des candidats idéals pour la constitution des values object. Je n’avais malheureusement jamais envisagé les utiliser.
    Car lorsque j’ai trop tardivement commencé à m’intéresser au DDD( merci Cyrille Matraire et ses vidéos) j’ai abordé les value object avec le paquet nuget functional programming de Vladimir Khorikov.
    Que pensez-vous de son article https://enterprisecraftsmanship.com/posts/net-value-type-ddd-value-object/ et de son “cons” sur l’usage des struct pour des values object. ( il en a également fait un sur les records, un petit article francophone made in Arrola à ce sujet serait le bienvenu 😉 ).
    Au plaisir de vous lire

  2. Dorra BARTAGUIZ
    19 avril 2021 at 12 h 23 min

    Merci Christophe pour ton retour. Je suis ravie que l’article t’intéresse 🙂
    Je suis d’accord avec l’article de Valdimir concernant les ORM et d’ailleurs c’est pourquoi j’insiste sur le fait d’utiliser les structs uniquement dans le domaine. Je considère que les objets retournés par les ORM sont des DTO (Data Transfert Object) et donc je déconseille d’utiliser les structs dans ces objets.

    Pour les perfs, la questions se posait il y a quelques années effectivement avec des machines/serveurs moins performants. Aujourd’hui, je ne suis pas certaine que la différence soit énorme. D’ailleurs, tu me donnes envie de faire un benchmark là-dessus, tiens 🙂
    En plus étant fun des object calisthenics, je ne vais pas créer des structures avec une dizaine de champs. Je conseille toujours d’avoir des values objets avec un minimum de champs (2 voire 3 max).

    Enfin, encore une fois, ça dépend du contexte. Le but de mon article aussi est de remettre le sujet sur la table car je trouve qu’on a oublié les structures qui sont et peuvent être utiles dans plusieurs cas d’utilisation mais par méconnaissance ou par habitude on ne les utilise jamais.

    Au plaisir de discuter de vive voix sur le sujet si tu veux 🙂