Nov 22, 2023

Les structures en C# sont amusantes - Partie 4/9: Comportement des constructeurs dans structures

Leia este post em português

Read this post in English.

  1. Les structures en C# sont amusantes
  2. Brève introduction aux Value Types vs Reference Types.
  3. Initialisation des champs dans les structures.
  4. Comportement des constructeurs dans structures (cet post).
  5. Des autres scénarios dans lesquels le comportement des constructeurs de structure peut vous surprendre.
  6. Struct avec des valeurs d'argument par défaut dans les constructeurs, ou, n'êtes-vous pas encore confus ?
  7. Le modificateur `required`de C # 11 ne sauvegardera pas votre c*l emploi.
  8. Structure utilisée comme valeurs d'argument défaut.
  9. Bonus: L'evolution des structures en C#

Dans le post précédent de notre série sur les Value Types, nous avons présenté le code ci-dessous:

qui imprime 0 (par opposition à 32 comme certains pourraient s'y attendre) et a également exploré les bases de la gestion de l'initialisation des champs en C#. Cet article élargit cette discussion en explorant certains aspects clés qui contribuent à la disparité entre le comportement attendu/observé.

Commençons par la déclaration suivante du post précédent:

D'une manière trop simpliste, chaque fois que le compilateur C# trouve une initialisation de champ, il déplacera simplement le code d'initialisation vers les constructeurs, c'est-à-dire que l'initialisation d'un champ équivaut à définir sa valeur dans les constructeurs (les champs statiques sont initialisés dans les constructeurs statiques)...

Si cela est vrai (spoiler : c'est le cas) et que le code instancie une nouvelle structure (ligne 1), pourquoi le champ n'est-il pas initialisé ? La réponse courte est que malgré l'expression new, aucun constructeur n'est exécuté, ce qui peut être facilement vérifié en examinant le il généré ci-dessous:

Notez que l'expression 'new S2()'  a été compilée comme l'instruction IL intobj S2 (IL_0002 dans la méthode '<Main>$' mise en évidence dans la capture d'écran ci-dessus). La documentation de cette instruction indique1:

Initialise tous les champs du type de valeur figurant à l'adresse spécifiée en utilisant la référence null ou la valeur 0 du type primitif qui convient...

Contrairement à Newobj, initobj n’appelle pas la méthode du constructeur. Initobj est destiné à l’initialisation des types de valeurs, tandis qu’il newobj est utilisé pour allouer et initialiser des objets.

nous conduisant à la question suivante, naturelle : pourquoi le compilateur a-t-il émis une instruction initobj au lieu d'une newobj?

Si vous prêtez une attention particulière à la déclaration de la structure S2 (lignes 5 à 9), vous remarquerez qu'il n'y a en fait aucun constructeur sans paramètre déclaré, donc la réponse à cette question devient claire : parce que le compilateur ne peut pas invoquer un constructeur non existant, et pour les Value Types, il peut recourir à initobj à la place!

Pour prouver ce point, vous pouvez simplement modifier le code en introduisant un constructeur sans paramètre dans S22:

et observez que maintenant IL_0002 contient l'instruction call instance void S2::.ctor() exécutant effectivement le constructeur sans paramètre sur s2 et d'où, exécutant l'initialisation du champ et provoquant la sortie du programme 32.

Donc en résumé :

  1. Les initialiseurs de champ sont injectés dans les constructeurs du type dans lequel le champ est déclaré ;
  2. Puisqu'aucun constructeur n'est exécuté, tous les champs de structure sont simplement mis à zéro, ce qui explique pourquoi le programme en haut imprime zéro au lieu de 32.

Comme toujours, tous les commentaires sont bienvenus.

Amuse toi!


No comments: