Étant donné que des collections comme System.Collections.Generic.HashSet<>
accept null
comme un membre d'ensemble, on peut se demander quel null
devrait être le code de hachage de . Il semble que le framework utilise 0
:
// nullable struct type
int? i = null;
i.GetHashCode(); // gives 0
EqualityComparer<int?>.Default.GetHashCode(i); // gives 0
// class type
CultureInfo c = null;
EqualityComparer<CultureInfo>.Default.GetHashCode(c); // gives 0
Cela peut être (un peu) problématique avec les énumérations nullables. Si nous définissons
enum Season
{
Spring,
Summer,
Autumn,
Winter,
}
alors le Nullable<Season>
(également appelé Season?
) ne peut prendre que cinq valeurs, mais deux d'entre elles, à savoir null
et Season.Spring
, ont le même code de hachage.
Il est tentant d'écrire un «meilleur» comparateur d'égalité comme celui-ci:
class NewNullEnumEqComp<T> : EqualityComparer<T?> where T : struct
{
public override bool Equals(T? x, T? y)
{
return Default.Equals(x, y);
}
public override int GetHashCode(T? x)
{
return x.HasValue ? Default.GetHashCode(x) : -1;
}
}
Mais y a-t-il une raison pour laquelle le code de hachage de null
devrait être 0
?
MODIFIER / AJOUTER:
Certaines personnes semblent penser qu'il s'agit de passer outre Object.GetHashCode()
. Ce n'est vraiment pas le cas, en fait. (Les auteurs de .NET ont fait une substitution de GetHashCode()
dans la Nullable<>
structure qui est pertinente, cependant.) Une implémentation écrite par l'utilisateur du paramètre sans paramètre GetHashCode()
ne peut jamais gérer la situation où se trouve l'objet dont nous recherchons le code de hachage null
.
Il s'agit d'implémenter la méthode abstraite EqualityComparer<T>.GetHashCode(T)
ou d'implémenter autrement la méthode d'interface IEqualityComparer<T>.GetHashCode(T)
. Maintenant, en créant ces liens vers MSDN, je vois qu'il y est dit que ces méthodes lancent un ArgumentNullException
si leur seul argument est null
. Cela doit certainement être une erreur sur MSDN? Aucune des propres implémentations de .NET ne lève d'exceptions. Lancer dans ce cas casserait effectivement toute tentative d'ajouter null
à un HashSet<>
. À moins de HashSet<>
faire quelque chose d'extraordinaire lorsqu'il s'agit d'un null
objet (je vais devoir le tester).
NOUVELLE MODIFICATION / AJOUT:
Maintenant, j'ai essayé le débogage. Avec HashSet<>
, je peux confirmer qu'avec le comparateur d'égalité par défaut, les valeurs Season.Spring
et null
se termineront dans le même compartiment. Cela peut être déterminé en inspectant très soigneusement les membres du tableau privé m_buckets
et m_slots
. Notez que les indices sont toujours, par conception, décalés de un.
Le code que j'ai donné ci-dessus ne résout cependant pas ce problème. En fin de compte, HashSet<>
ne demandera même jamais au comparateur d'égalité quand la valeur est null
. Ceci provient du code source de HashSet<>
:
// Workaround Comparers that throw ArgumentNullException for GetHashCode(null).
private int InternalGetHashCode(T item) {
if (item == null) {
return 0;
}
return m_comparer.GetHashCode(item) & Lower31BitMask;
}
Cela signifie qu'au moins pour HashSet<>
, il n'est même pas possible de modifier le hachage de null
. Au lieu de cela, une solution consiste à modifier le hachage de toutes les autres valeurs, comme ceci:
class NewerNullEnumEqComp<T> : EqualityComparer<T?> where T : struct
{
public override bool Equals(T? x, T? y)
{
return Default.Equals(x, y);
}
public override int GetHashCode(T? x)
{
return x.HasValue ? 1 + Default.GetHashCode(x) : /* not seen by HashSet: */ 0;
}
}
Réponses:
Tant que le code de hachage renvoyé pour les valeurs nulles est cohérent pour le type, tout devrait aller. La seule exigence pour un code de hachage est que deux objets considérés comme égaux partagent le même code de hachage.
Renvoyer 0 ou -1 pour null, tant que vous en choisissez un et que vous le retournez tout le temps, fonctionnera. De toute évidence, les codes de hachage non nuls ne doivent pas renvoyer la valeur que vous utilisez pour null.
Questions similaires:GetHashCode sur les champs nuls?
Que doit retourner GetHashCode lorsque l'identifiant de l'objet est nul?
Les «remarques» de cette entrée MSDN vont plus en détail autour du code de hachage. De manière poignante, la documentation ne fournit aucune couverture ou discussion des valeurs nulles du tout - même pas dans le contenu de la communauté.Pour résoudre votre problème avec l'énumération, ré-implémentez le code de hachage pour renvoyer une valeur différente de zéro, ajoutez une entrée d'énumération "inconnue" par défaut équivalente à null, ou n'utilisez simplement pas d'énumérations nullables.
Une trouvaille intéressante, au fait.
Un autre problème que je vois généralement avec cela est que le code de hachage ne peut pas représenter un type de 4 octets ou plus qui est nullable sans au moins une collision (d'autant plus que la taille du type augmente). Par exemple, le code de hachage d'un int est juste le int, il utilise donc la plage int complète. Quelle valeur dans cette plage choisissez-vous pour null? Celui que vous choisissez entrera en conflit avec le code de hachage de la valeur lui-même.
Les collisions en elles-mêmes ne sont pas nécessairement un problème, mais vous devez savoir qu'elles sont là. Les codes de hachage ne sont utilisés que dans certaines circonstances. Comme indiqué dans la documentation sur MSDN, les codes de hachage ne sont pas garantis pour renvoyer des valeurs différentes pour différents objets et ne devraient donc pas être attendus.
la source
Object.GetHashCode()
dans votre propre classe (ou structure), vous savez que ce code ne sera frappé que lorsque les gens ont réellement une instance de votre classe. Cette instance ne peut pas êtrenull
. C'est pourquoi vous ne commencez pas votre remplacement deObject.GetHashCode()
parif (this == null) return -1;
Il y a une différence entre «êtrenull
» et «être un objet possédant des champs qui sontnull
».T
, alors(T?)null
et(T?)default(T)
aura le même code de hachage (dans l'implémentation actuelle de .NET). Cela pourrait être changé si les implémenteurs de .NET modifiaient le code de hachagenull
ou l'algorithme de code de hachage duSystem.Enum
.HashSet<>
) cela ne fonctionne pas pour changer le code de hachage denull
.Gardez à l'esprit que le code de hachage est utilisé comme première étape dans la détermination de l'égalité uniquement, et [n'est / ne devrait] jamais (être) utilisé comme une détermination de facto pour savoir si deux objets sont égaux.
Si les codes de hachage de deux objets ne sont pas égaux, alors ils sont traités comme non égaux (car nous supposons que l'implémentation sous-jacente est correcte - c'est-à-dire que nous ne le devinons pas). S'ils ont le même code de hachage, ils doivent alors être vérifiés pour l' égalité réelle qui, dans votre cas, la
null
valeur et la valeur enum échoueront.En conséquence, l'utilisation de zéro est aussi bonne que toute autre valeur dans le cas général.
Bien sûr, il y aura des situations, comme votre énumération, où ce zéro est partagé avec le code de hachage d'une valeur réelle . La question est de savoir si, pour vous, la minuscule surcharge d'une comparaison supplémentaire pose des problèmes.
Si tel est le cas, définissez votre propre comparateur pour le cas du nullable pour votre type particulier, et assurez-vous qu'une valeur nulle donne toujours un code de hachage qui est toujours le même (bien sûr!) Et une valeur qui ne peut pas être fournie par le sous-jacent. propre algorithme de code de hachage de type. Pour vos propres types, c'est faisable. Pour les autres - bonne chance :)
la source
Il n'est pas nécessaire que ce soit zéro - vous pouvez en faire 42 si vous le souhaitez.
Tout ce qui compte, c'est la cohérence lors de l'exécution du programme.
C'est juste la représentation la plus évidente, car elle
null
est souvent représentée par un zéro en interne. Ce qui signifie, pendant le débogage, si vous voyez un code de hachage de zéro, cela pourrait vous inciter à penser, "Hmm ... était-ce un problème de référence nul?"Notez que si vous utilisez un nombre comme
0xDEADBEEF
, alors quelqu'un pourrait dire que vous utilisez un nombre magique ... et vous le seriez en quelque sorte. (Vous pourriez dire que zéro est un nombre magique aussi, et vous auriez raison ... sauf qu'il est si largement utilisé qu'il est en quelque sorte une exception à la règle.)la source
Bonne question.
J'ai juste essayé de coder ceci:
enum Season { Spring, Summer, Autumn, Winter, }
et exécutez ceci comme ceci:
Season? v = null; Console.WriteLine(v);
il retourne
null
si je le fais, plutôt normal
Season? v = Season.Spring; Console.WriteLine((int)v);
il revient
0
, comme prévu, ou simple Spring si on évite de lancer versint
.Donc ... si vous faites ce qui suit:
Season? v = Season.Spring; Season? vnull = null; if(vnull == v) // never TRUE
ÉDITER
Depuis MSDN
Si deux objets se comparent comme égaux, la méthode GetHashCode de chaque objet doit renvoyer la même valeur. Toutefois, si deux objets ne se comparent pas comme égaux, les méthodes GetHashCode pour les deux objets ne doivent pas renvoyer des valeurs différentes
En d'autres termes: si deux objets ont le même code de hachage qui ne signifie pas qu'ils sont égaux, la cause de l' égalité réelle est déterminée par Equals .
De nouveau à partir de MSDN:
la source
Cela aurait pu être n'importe quoi. J'ai tendance à convenir que 0 n'était pas nécessairement le meilleur choix, mais c'est celui qui conduit probablement au moins de bogues.
Une fonction de hachage doit absolument renvoyer le même hachage pour la même valeur. Une fois qu'il existe un composant qui fait cela, c'est vraiment la seule valeur valide pour le hachage de
null
. S'il y avait une constante pour cela, comme, hmobject.HashOfNull
, alors quelqu'un implémentant unIEqualityComparer
devrait savoir comment utiliser cette valeur. S'ils n'y pensent pas, la chance qu'ils utilisent 0 est légèrement plus élevée que toutes les autres valeurs, je pense.Comme mentionné ci-dessus, je pense que c'est complètement impossible, tout simplement parce qu'il existe des types qui suivent déjà la convention selon laquelle le hachage de null est 0.
la source
EqualityComparer<T>.GetHashCode(T)
pour un type particulierT
qui le permetnull
, on doit faire quelque chose quand l'argument estnull
. Vous pouvez (1) lancer unArgumentNullException
, (2) retourner0
ou (3) retourner quelque chose d'autre. Je prends ta réponse pour une recommandation de toujours revenir0
dans cette situation?C'est 0 par souci de simplicité. Il n'y a pas d'exigence aussi stricte. Il vous suffit de vous assurer des exigences générales du codage de hachage.
Par exemple, vous devez vous assurer que si deux objets sont égaux, leurs hashcodes doivent toujours être égaux également. Par conséquent, différents codes de hachage doivent toujours représenter des objets différents (mais ce n'est pas nécessairement vrai vice versa: deux objets différents peuvent avoir le même code de hachage, même si cela se produit souvent, ce n'est pas une fonction de hachage de bonne qualité - elle n'a pas bonne résistance aux collisions).
Bien sûr, j'ai limité ma réponse aux exigences de nature mathématique. Il existe également des conditions techniques spécifiques à .NET, que vous pouvez lire ici . 0 pour une valeur nulle n'en fait pas partie.
la source
Cela pourrait donc être évité en utilisant une
Unknown
valeur enum (même si cela semble un peu étrange pour aSeason
d'être inconnu). Donc, quelque chose comme ça annulerait ce problème:public enum Season { Unknown = 0, Spring, Summer, Autumn, Winter } Season some_season = Season.Unknown; int code = some_season.GetHashCode(); // 0 some_season = Season.Autumn; code = some_season.GetHashCode(); // 3
Ensuite, vous auriez des valeurs de code de hachage uniques pour chaque saison.
la source
Personnellement, je trouve que l'utilisation de valeurs nullables est un peu gênante et j'essaie de les éviter chaque fois que je le peux. Votre problème n'est qu'une autre raison. Parfois, ils sont très pratiques, mais ma règle de base est de ne pas mélanger les types valeur avec null si possible simplement parce qu'ils proviennent de deux mondes différents. Dans le framework .NET, ils semblent faire la même chose - de nombreux types de valeur fournissent une
TryParse
méthode qui permet de séparer les valeurs de l'absence de valeur (null
).Dans votre cas particulier, il est facile de se débarrasser du problème car vous gérez votre propre
Season
type.(Season?)null
pour moi signifie «la saison n'est pas spécifiée», comme lorsque vous avez un formulaire Web où certains champs ne sont pas obligatoires. À mon avis, il est préférable de spécifier cette «valeur» spéciale enenum
soi plutôt que d'utiliser un peu maladroitNullable<T>
. Il sera plus rapide (pas de boxe) plus facile à lire (Season.NotSpecified
vsnull
) et résoudra votre problème avec les codes de hachage.Bien sûr pour d'autres types, comme
int
vous ne pouvez pas étendre le domaine de valeur et désigner l'une des valeurs comme spéciale n'est pas toujours possible. Mais avec leint?
code de hachage, la collision est un problème beaucoup plus petit, voire pas du tout.la source
Nullable<>
struct (où leHasValue
membre sera alors définitrue
). Êtes-vous sûr que le problème est vraiment plus petit avecint?
? La plupart du temps, on n'utilise que quelques valeurs deint
, puis cela équivaut à une énumération (qui peut en théorie avoir plusieurs membres).int
cela a plus de sens. Bien sûr, les préférences varient.Tuple.Create( (object) null! ).GetHashCode() // 0 Tuple.Create( 0 ).GetHashCode() // 0 Tuple.Create( 1 ).GetHashCode() // 1 Tuple.Create( 2 ).GetHashCode() // 2
la source