Pourquoi est-il possible d’instancier une structure sans le nouveau mot clé?

Pourquoi ne sums-nous pas obligés d’instancier une structure, comme lors de l’utilisation d’une classe?

Le pourquoi est simplement – parce que la spécification le dit . Le comment consiste à s’assurer que tout le bloc de mémoire est “définitivement assigné”, ce qui signifie: assigner une valeur à chaque champ de la structure. Cependant, cela nécessite 2 choses désagréables:

  • champs publics (presque toujours mauvais)
  • champs mutables (généralement mauvais dans une structure)

Par conséquent, dans la plupart des cas de meilleure pratique , vous devez utiliser la new(...) syntaxe new(...) pour appeler le constructeur (ou pour mettre à zéro la mémoire, pour le constructeur sans paramètre) pour le type correctement.

Pourquoi ne sums-nous pas obligés d’instancier une structure avec “new”, comme lors de l’utilisation d’une classe?

Lorsque vous “créez” un type de référence, trois choses se produisent. Tout d’abord, le gestionnaire de mémoire alloue de l’espace à partir d’ un stockage à long terme . Deuxièmement, une référence à cet espace est transmise au constructeur, qui initialise l’instance. Troisièmement, cette référence est renvoyée à l’appelant.

Lorsque vous “créez” un type de valeur, trois choses se produisent. Tout d’abord, le gestionnaire de mémoire alloue de l’espace à partir d’ un stockage à court terme . Deuxièmement, une référence à l’emplacement de stockage à court terme est transmise au constructeur. Une fois le constructeur exécuté, la valeur qui se trouvait dans l’emplacement de stockage à court terme est copiée dans l’emplacement de stockage de la valeur, où qu’elle se trouve. N’oubliez pas que les variables de type valeur stockent la valeur réelle .

(Notez que le compilateur est autorisé à optimiser ces trois étapes en une seule si le compilateur peut déterminer que cela n’expose jamais une structure partiellement construite au code utilisateur. En d’autres termes, le compilateur peut générer du code qui passe simplement une référence à la dernière l’emplacement de stockage du constructeur, économisant ainsi une atsortingbution et une copie.)

Nous pouvons donc maintenant répondre à votre question, que vous avez en fait posée à l’envers. Il serait préférable de demander:

Pourquoi sums-nous obligés d’allouer une classe avec “nouveau”, au lieu de pouvoir simplement initialiser les champs comme avec une structure?

Vous devez atsortingbuer une classe avec “nouveau” à cause de ces trois éléments de la liste. Vous avez besoin d’une nouvelle mémoire allouée à partir du stockage à long terme et vous devez transmettre une référence à ce stockage au constructeur. “nouveau” est l’opérateur qui sait comment faire cela.

Vous n’avez pas besoin d’appeler “new” sur une structure car il n’est pas nécessaire d’allouer le stockage “final”; le stockage final existe déjà . La nouvelle valeur va aller quelque part et vous avez déjà obtenu ce stockage par un autre moyen. Les types de valeur n’ont pas besoin d’une nouvelle allocation; tout ce dont ils ont besoin, c’est d’une initialisation. Tout ce que vous avez à faire est de vous assurer que le stockage est correctement initialisé , et vous pouvez souvent le faire sans faire appel à un constructeur. Bien entendu, cela signifie que vous courez le risque d’avoir une variable de type valeur qui peut être considérée comme étant dans un état partiellement initialisé par le code utilisateur.

Résumer: appeler un ctor est facultatif pour les types de valeur car aucune nouvelle mémoire ne doit être allouée lors de l’initialisation d’une instance d’un type de valeur et parce que sauter l’appel du constructeur signifie que vous devez sauter une allocation à court terme et une copie . Le prix que vous payez pour ce gain de performance est que le code utilisateur peut voir une structure partiellement initialisée.

Parce que les structures sont des types de valeur et que les classes sont des types de référence. Donc, les structures entrent dans la même catégorie que int, double, etc.

Parce qu’une structure est un type de valeur. Lorsque vous en déclarez une variable, l’instance est immédiatement là.

Un constructeur (l’opérateur new ) est donc optionnel pour une structure.

Considérer

 struct V { public int x = 0; } class R { public int y = 0; } void F() { V a; // a is an instance of VR b; // b is a reference to an R ax = 1; // OK, the memory exists //by = 2; // error, there is no instance yet b = new R(); // allocates new memory a = new V(); // overwrites memory } 

À venir dans un an et demi plus tard …

Une struct est généralement passée par valeur, alors qu’une class est toujours passée par référence. Vous avez probablement une bonne compréhension de ce qui se passe lorsqu’un object est passé par référence. Lorsqu’un object est transmis par valeur, son contenu, plutôt que l’object lui-même, est transmis. Pour le programmeur, il semble qu’une copie superficielle de l’object ait été réalisée. Changer une instance ne changera pas l’autre.

Toutes les variables (y compris les champs et les propriétés) ont toujours un espace alloué pour elles tant qu’elles existent. Il est important de noter que les variables locales n’existent pas jusqu’à ce qu’une valeur leur soit affectée dans les nouvelles versions de C #. Dans le cas de variables de type class , l’espace alloué contiendra une référence au contenu de l’object. Dans le cas d’une variable struct -type, l’espace alloué contiendra le contenu réel de l’object.

Donc, disons que vous avez une variable de type class “vide”. Il aura une référence par défaut. Cette référence équivaudra à null . Cependant, une variable de type struct n’est pas une référence: c’est le contenu réel d’un object. Lorsqu’ils sont laissés “vides”, tous ses champs (et leurs propriétés implémentées automatiquement, qui sont sauvegardées par des champs situés dans les coulisses) contiennent tous des valeurs par défaut. En d’autres termes, ils sont également “vides”. Si ce sont des types de référence, ils seront null ; s’ils sont des types de valeur, ils seront 0 ou des structures à zéro (et la chaîne continue).

C’est aussi pourquoi une structs ne peut pas avoir de constructeur par défaut. Tout comme vous ne pouvez pas redéfinir l’apparence d’une class lorsqu’elle est null , vous ne pouvez pas redéfinir l’apparence d’une struct lorsqu’elle est mise à zéro.

Il existe un opérateur sous-utilisé pour obtenir la valeur par défaut de tout type: class , struct ou insortingnsèque. C’est l’opérateur default() . Par exemple:

 class ClassType { } struct StructType { } // // ... // var classA = default(ClassType); var classB = (ClassType)null; if (classA == classB) { // This will execute, because both equal null. } var structA = default(StructType); var structB = new StructType(); if (structA == structB) { // This will execute, because both are zeroed. } // // ... // ///  /// An example use case for the default() operator. ///  ///  /// null if T is a reference type, a zeroed instance T is a /// struct, or 0 if T is an insortingnsic type. ///  private static T GetDefault() { // This line wouldn't comstack, because T could be a value type. //return null; // This line wouldn't comstack, because T could be a reference type without a default or accessible constructor. //return new(T); // This will work! return default(T); } 

Comme l’ont dit David Heffernan et Henk Holterman, ses structures sont des types de valeur et sont donc instanciées lors de leur déclaration. Pour une meilleure compréhension de ValueType et ReferenceType, veuillez vérifier ce lien . Paddy l’ a bien expliqué.

En plus de ce qui a été posté: Notez qu’une structure ne peut avoir un constructeur sans paramètre ou un initialiseur pour aucun de ses champs d’instance. La valeur par défaut consiste à définir tous les champs de type valeur sur leur valeur par défaut (par exemple, 0 pour les entiers, false pour les booléens, etc.) et tous les champs de type de référence sur null.

Deuxièmement, une structure est initialisée, par exemple en appelant un constructeur ou en utilisant default() .