Types Nullables Boxing / Unboxing – Pourquoi cette implémentation?

Extrait de CLR via C # sur les types de valeur Boxing / Unboxing …

Sur la boxe: Si l’instance nullable n’est pas null , le CLR extrait la valeur de l’instance nullable et la boxe. En d’autres termes, un Nullable avec une valeur de 5 est encadré dans un boxed-Int32 avec une valeur de 5.

Sur le déballage: Le déballage consiste simplement à obtenir une référence à la partie non encadrée d’un object encadré. Le problème est qu’un type de valeur boxed ne peut pas être simplement décompressé dans une version nullable de ce type de valeur car la valeur boxed ne contient pas le champ boolean hasValue . Ainsi, lors du déconditionnement d’un type de valeur dans une version nullable, le CLR doit allouer un object Nullable , initialiser le champ hasValue sur true et définir le champ de valeur sur la même valeur que celle du type de valeur encadré. Cela a un impact sur les performances de votre application (allocation de mémoire pendant le déballage).

Pourquoi l’équipe CLR a-t-elle eu autant de problèmes avec les types Nullable? Pourquoi n’a-t-il pas été tout simplement placé dans un Nullable?

Je me souviens que ce comportement était une sorte de changement de dernière minute. Dans les versions antérieures de .NET 2.0, Nullable était un type de valeur “normal”. Boxe une valeur null int? transformé en une boîte int? avec un drapeau booléen. Je pense que la raison pour laquelle ils ont décidé de choisir l’approche actuelle est la cohérence. Dire:

 int? test = null; object obj = test; if (test != null) Console.WriteLine("test is not null"); if (obj != null) Console.WriteLine("obj is not null"); 

Dans la première approche (box null -> boxed Nullable ), vous n’obtiendrez pas le “test n’est pas nul” mais vous obtiendrez le “object n’est pas nul”, ce qui est étrange.

De plus, s’ils ont mis en boîte une valeur boxed-Nullable :

 int? val = 42; object obj = val; if (obj != null) { // Our object is not null, so intuitively it's an `int` value: int x = (int)obj; // ...but this would have failed. } 

À côté de cela, je pense que le comportement actuel est parfaitement logique pour des scénarios tels que les valeurs de firebase database nullable (pensez à SQL-CLR …)


Clarification:

L’intérêt de fournir des types nullables est de faciliter le traitement de variables qui n’ont aucune valeur significative. Ils ne voulaient pas fournir deux types distincts, non liés. Un int? devrait se comporter plus ou moins comme un simple int . C’est pourquoi C # fournit des opérateurs levés.

Ainsi, lors du déconditionnement d’un type de valeur dans une version nullable, le CLR doit allouer un object Nullable , initialiser le champ hasValue sur true et définir le champ de valeur sur la même valeur que celle indiquée dans le type de valeur encadré. Cela a un impact sur les performances de votre application (allocation de mémoire pendant le déballage).

Ce n’est pas vrai. Le CLR devrait allouer de la mémoire sur la stack pour contenir la variable, qu’elle soit ou non nullable. Il n’y a pas de problème de performances pour allouer de l’espace pour une variable booléenne supplémentaire.

Je pense qu’il est logique de placer une valeur null dans une référence null. Avoir une valeur en boîte disant “Je sais que je serais un Int32 si j’avais une valeur, mais je ne le fais pas” ne me semble pas intuitif. Mieux vaut passer de la version du type de valeur “pas une valeur” (une valeur avec HasValue comme false) à la version du type de référence de “pas une valeur” (une référence null).

Je crois que ce changement a été fait sur les commentaires de la communauté, au fait.

Cela permet également une utilisation intéressante de as même pour les types de valeur:

 object mightBeADouble = GetMyValue(); double? unboxed = mightBeADouble as double?; if (unboxed != null) { ... } 

Ceci est plus cohérent avec la façon dont les “conversions incertaines” sont gérées avec les types de référence, à la précédente:

 object mightBeADouble = GetMyValue(); if (mightBeADouble is double) { double unboxed = (double) mightBeADouble; ... } 

(Il se peut également que sa performance soit meilleure car il n’ya qu’une vérification du type de temps d’exécution.)

Une chose que vous gagnez avec ce comportement est que la version encadrée implémente toutes les interfaces sockets en charge par le type sous-jacent. (Le but est que Nullable apparaisse identique à int à toutes fins pratiques.) boxed-Nullable au lieu d’un boxed-int empêcherait ce problème.

À partir de la page MSDN,

 double? d = 44.4; object iBoxed = d; // Access IConvertible interface implemented by double. IConvertible ic = (IConvertible)iBoxed; int i = ic.ToInt32(null); ssortingng str = ic.ToSsortingng(); 

Obtenir également l’int de à partir d’une version encadrée d’un Nullable est simple. Généralement, vous ne pouvez pas décompresser dans un type autre que le type d’origine src.

 float f = 1.5f; object boxed_float = f; int int_value = (int) boxed_float; // will blow up. Cannot unbox a float to an int, you *must* unbox to a float first. float? nullableFloat = 1.4f; boxed_float = nullableFloat; float fValue = (float) boxed_float; // can unbox a float? to a float Console.WriteLine(fValue); 

Ici, vous n’avez pas besoin de savoir si la version d’origine était une version int ou une version nullable de celle-ci. (+ vous obtenez également des performances; économisez de l’espace pour stocker le booléen hasValue dans l’object boxed)

Je suppose que c’est essentiellement ce que ça fait. La description donnée inclut votre suggestion (c’est-à-dire la boxe dans un Nullable ).

L’extra est qu’il définit le champ hasValue après la boxe.

Je dirais que la raison de ce comportement provient du comportement de Object.Equals, notamment le fait que si le premier object est nul et le second ne l’est pas, Object.Equals renvoie false plutôt que d’appeler la méthode Equals sur le second. object.

Si Object.Equals aurait appelé la méthode Equals sur le deuxième object dans le cas où le premier object était nul mais le deuxième non, un object dont la valeur est Nullable Nullable aurait pu renvoyer True par rapport à null. Personnellement, je pense que le remède approprié aurait été de faire en sorte que la propriété HasValue d’un Nullable n’ait plus rien à voir avec le concept de référence nulle. En ce qui concerne les frais généraux liés au stockage d’un drapeau booléen sur le tas, on aurait pu prévoir que pour chaque type Nullable , il y aurait une version statique en boîte vide, puis prévoir que le déballage de la copie vide en boîte statique donnerait une Nullable vide , et le déballage de toute autre instance produirait une instance peuplée.