Apparemment, vous ne pouvez pas utiliser un null
pour une clé, même si votre clé est de type nullable.
Ce code:
var nullableBoolLabels = new System.Collections.Generic.Dictionary<bool?, string>
{
{ true, "Yes" },
{ false, "No" },
{ null, "(n/a)" }
};
... entraîne cette exception:
La valeur ne peut pas être nulle. Nom du paramètre: clé
Description: une exception non gérée s'est produite lors de l'exécution de la demande Web en cours. Veuillez consulter la trace de la pile pour plus d'informations sur l'erreur et son origine dans le code.
[ArgumentNullException: Value cannot be null. Parameter name: key]
System.ThrowHelper.ThrowArgumentNullException(ExceptionArgument argument) +44
System.Collections.Generic.Dictionary'2.Insert(TKey key, TValue value, Boolean add) +40
System.Collections.Generic.Dictionary'2.Add(TKey key, TValue value) +13
Pourquoi le framework .NET autoriserait-il un type nullable pour une clé, mais pas une valeur null?
Cela vous dirait la même chose si vous aviez un Dictionary<SomeType, string>
, SomeType
étant un type de référence, et vous avez essayé de passer null
comme clé, ce n'est pas quelque chose qui affecte uniquement le type nullable comme bool?
. Vous pouvez utiliser n'importe quel type de clé, nullable ou non.
Tout se résume au fait que vous ne pouvez pas vraiment comparer nulls
. Je suppose que la logique derrière le fait de ne pas pouvoir mettre null
dans la clé, une propriété conçue pour être comparée à d'autres objets, est qu'il rend incohérent de comparer les références null
.
Si vous voulez une raison dans les spécifications, cela se résume à "Une clé ne peut pas être une référence nulle" sur MSDN .
Si vous voulez un exemple d'une solution de contournement possible, vous pouvez essayer quelque chose de similaire à Besoin d'une implémentation IDictionary qui permettra une clé nulle
Souvent, vous devez revenir aux méthodologies et techniques C++ pour comprendre pleinement comment et pourquoi le .NET Framework fonctionne d'une manière particulière.
En C++, vous devez souvent choisir une clé qui ne sera pas utilisée - le dictionnaire utilise cette clé pour pointer vers les entrées supprimées et/ou vides. Par exemple, vous avez un dictionnaire de <int, int>
, et après avoir inséré une entrée, vous la supprimez. Plutôt que d'exécuter le nettoyage des ordures ici et là, restructurer le dictionnaire et entraîner de mauvaises performances; le dictionnaire remplacera simplement la valeur KEY par la clé que vous avez précédemment sélectionnée, ce qui signifie essentiellement "lorsque vous traversez l'espace mémoire du dictionnaire, faites semblant <key,value>
la paire n'existe pas, n'hésitez pas à l'écraser. "
Une telle clé est également utilisée dans les dictionnaires qui pré-allouent l'espace dans les compartiments d'une manière particulière - vous avez besoin d'une clé pour "initialiser" les compartiments au lieu d'avoir un indicateur pour chaque entrée qui indique si son contenu est valide ou non. Donc, au lieu d'avoir un triple <key, value, initialized>
vous auriez un Tuple <key, value>
avec pour règle que si key == empty_key alors il n'a pas été initialisé - et donc vous ne pouvez pas utiliser empty_key comme valeur KEY valide.
Vous pouvez voir ce genre de comportement dans la table de hachage Google (dictionnaire pour vous .NET personnes :) dans la documentation ici: http://google-sparsehash.googlecode.com/svn/trunk/doc/dense_hash_map.html
Regarde le set_deleted_key
et set_empty_key
fonctions pour obtenir ce dont je parle.
Je parierais que .NET utilise NULL en tant que clé unique supprimée ou clé vide afin de faire ce genre de trucs astucieux qui améliorent les performances.
Vous ne pouvez pas utiliser un booléen nul? car les types nullables sont censés agir comme des types de référence. Vous ne pouvez pas non plus utiliser une référence nulle comme clé de dictionnaire.
La raison pour laquelle vous ne pouvez pas utiliser une référence nulle comme clé de dictionnaire revient probablement à une décision de conception chez Microsoft. Autoriser les clés nulles nécessite de les vérifier, ce qui rend l'implémentation plus lente et plus compliquée. Par exemple, l'implémentation devrait éviter d'utiliser .Equals ou .GetHashCode sur une référence nulle.
Je suis d'accord qu'il serait préférable d'autoriser les clés nulles, mais il est trop tard pour changer le comportement maintenant. Si vous avez besoin d'une solution de contournement, vous pouvez écrire votre propre dictionnaire avec les clés nulles autorisées, ou vous pouvez écrire une structure wrapper qui convertit implicitement en/de T et en faire le type de clé pour votre dictionnaire (c'est-à-dire que la structure encapsule le null et gérer la comparaison et le hachage, de sorte que le dictionnaire ne "voit" jamais le null).
Il n'y a pas de raison fondamentale. HashSet autorise null et un HashSet est simplement un dictionnaire où la clé est du même type que la valeur. Donc, vraiment, les clés nulles auraient dû être autorisées mais seraient en rupture pour le changer maintenant, donc nous sommes coincés avec cela.
Dictionaires (description de base)
Un dictionnaire est l'implémentation générique (typée) de la classe Hashtable, introduite dans .NET Framework 2.0.
Une table de hachage stocke une valeur basée sur une clé (plus précisément un hachage de la clé).
Chaque objet dans .NET a la méthode GetHashCode
.
Lorsque vous insérez une paire valeur/clé dans une table de hachage, GetHashCode
est appelée sur la clé.
Pensez-y: vous ne pouvez pas appeler la méthode GetHashCode
sur null
.
Alors qu'en est-il des types Nullable?
La classe Nullable
est simplement un wrapper, pour permettre aux valeurs nulles d'être affectées aux types de valeurs. Fondamentalement, le wrapper se compose d'un HasValue
booléen qui indique s'il est nul ou non, et d'un Value
pour contenir la valeur du type de valeur.
Mettez-le ensemble et qu'obtenez-vous
. NET ne se soucie pas vraiment de ce que vous utilisez comme clé dans une table de hachage/dictionnaire.
Mais lorsque vous ajoutez une combinaison de valeurs de clé, elle doit pouvoir générer un hachage de la clé.
Peu importe si votre valeur est enveloppée dans un Nullable
, null.GetHashCode est impossible.
La propriété Indexer et les méthodes Add du Dictionary vérifient la valeur null et lèvent une exception lorsqu'elle trouve null.
Ne pas utiliser null fait partie du contrat selon la page MSDN: http://msdn.Microsoft.com/en-us/library/k7z0zy8k.aspx
Je suppose que la raison en est qu'avoir null comme valeur valide ne fera que compliquer le code sans raison.
Ah, les problèmes de code générique. Considérez ce bloc via Reflector:
private void Insert(TKey key, TValue value, bool add)
{
int freeList;
if (key == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
}
Il n'y a aucun moyen de réécrire ce code pour dire "les valeurs Nullable sont autorisées, mais ne permettent pas aux types de référence d'être NULL".
Ok, alors comment ne pas autoriser TKey à être un "bool?". Eh bien, encore une fois, il n'y a rien dans le langage C # qui vous permettrait de dire cela.
Les dictionnaires ne peuvent pas accepter les types de référence nuls pour diverses raisons, notamment parce qu'ils n'ont pas de méthode GetHashCode.
Une valeur null pour le type de valeur nullable est censée représenter une valeur null - la sémantique est censée être aussi synonyme que possible d'une référence null. Il serait légèrement étrange si vous pouviez utiliser une valeur nullable nulle où vous ne pouviez pas utiliser de références null simplement à cause d'un détail d'implémentation de types de valeur nullable.
Soit cela, soit le dictionnaire a:
if (key == null)
et ils n'y ont jamais vraiment pensé.
La valeur de la clé doit être unique, donc null ne peut pas être une clé valide car null n'indique aucune clé.
C'est pourquoi le framework .Net ne le fait pas autorise la valeur null et lève une exception.
En ce qui concerne pourquoi Nullable a permis, et non les captures au moment de la compilation, je pense que la raison en est que cette clause where
qui autorise tout T
sauf Nullable n'est pas possible (au moins je ne le fais pas savoir comment y parvenir).