Étant donné que des collections telles que System.Collections.Generic.HashSet<>
accepter null
en tant que membre d'un ensemble, on peut se demander quel est le code de hachage de null
devrait être. Il semble que le cadre 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 s'avérer (un peu) problématique avec les enums nullables. Si nous définissons
enum Season
{
Spring,
Summer,
Autumn,
Winter,
}
puis 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 que le code de hachage de null
devrait être 0
?
EDIT/ADDITION :
Certaines personnes semblent penser qu'il s'agit de passer outre à l'obligation d'information. Object.GetHashCode()
. En fait, ce n'est pas le cas. (Les auteurs de .NET ont fait une surcharge de GetHashCode()
dans le Nullable<>
structure qui es (mais cela n'a rien à voir avec le sujet). Une implémentation écrite par l'utilisateur de la méthode sans paramètre GetHashCode()
ne peut jamais gérer la situation où l'objet dont nous cherchons le code de hachage est null
.
Il s'agit de mettre en œuvre la méthode abstraite EqualityComparer<T>.GetHashCode(T)
ou en mettant en œuvre la méthode de l'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
. Il s'agit certainement d'une erreur de MSDN ? Aucune des implémentations de .NET ne lance d'exception. Dans ce cas, le fait de lancer des exceptions briserait effectivement toute tentative d'ajouter la fonction null
à un HashSet<>
. A moins que HashSet<>
fait quelque chose d'extraordinaire lorsqu'il s'agit d'un null
(il faudra que je teste cela).
NOUVELLE MODIFICATION/AJOUT :
J'ai ensuite essayé de déboguer. Avec HashSet<>
Je peux confirmer qu'avec le comparateur d'égalité par défaut, les valeurs Season.Spring
et null
volonté se terminent dans le même seau. Cela peut être déterminé en inspectant très attentivement les membres du tableau privé m_buckets
et m_slots
. Notez que les indices sont toujours, par construction, décalés d'une unité.
Le code que j'ai donné ci-dessus ne résout cependant pas ce problème. Il s'avère que HashSet<>
ne demandera même pas au comparateur d'égalité si la valeur est null
. Ceci est tiré 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 que, au moins pour HashSet<>
il n'est même pas possible de modifier le hash de null
. La solution consiste à modifier le hachage de toutes les autres valeurs, comme suit :
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;
}
}
0 votes
Duplication possible de Que doit renvoyer GetHashCode lorsque l'identifiant de l'objet est nul ?
26 votes
Pourquoi le code de hachage de null ne devrait-il pas être zéro ? Une collision de hachage n'est pas la fin du monde, vous savez.
3 votes
Sauf qu'il s'agit d'une collision bien connue et assez courante. Non pas qu'il s'agisse mauvais ou même un problème majeur, c'est juste facilement évitable
8 votes
Lol pourquoi suis-je en train de penser "si le .NET framework saute d'un pont, le suivrez-vous ?"...
3 votes
Juste par curiosité, qu'est-ce qu'une saison nulle ?
1 votes
Personnellement, c'est la raison pour laquelle je donne toujours à mes enums la valeur "Empty" ou "Unknown" comme première valeur. De cette manière, mon enum Saison ne représentera jamais Spring sans que j'en définisse explicitement la valeur, ce qui annule le problème.
0 votes
@Adam, quelle est la hauteur du pont ? ;)
1 votes
Je sais que les collisions de hachage sont parfois inévitables, et je sais qu'une collision de hachage n'entraînera pas la fin de l'univers en tant que tel. Mais tout type d'énumération bien écrit debe spécifier un nom pour la constante
0
de ce type. Sinon, la valeur par défautdefault(T)
de la non annulable serait sans nom, ce qui est considéré comme une mauvaise conception de l'enum. Cela signifie donc que tout enum nullableT?
quelle que soit la "petite" taille de cette énumération, doit conduire à une collision de code de hachage. N'est-ce pas regrettable ?1 votes
@JeppeStigNielsen C'est moins malheureux dans la mesure où les enums ont tendance à ne pas être que et sont également des clés peu fréquentes dans une table de hachage. Même s'ils sont hachés, parce qu'il y en a si peu au total, même le pire des cas (recherche O(n) puisque tout va dans le même seau) n'est pas susceptible d'affecter le programme de manière perceptible.
0 votes
J'aimerais beaucoup que l'on procède à une analyse comparative de l'impact réel de ces mesures. Je pourrais imaginer des cas où c'est réellement pertinent. Mais dans ces cas-là, vous utiliseriez probablement une implémentation de dictionnaire personnalisée utilisant un accès direct au tableau via les valeurs de l'énumération.
1 votes
Quel chiffre souhaitez-vous obtenir pour la nullité ?
1 votes
Le point essentiel est que le code de hachage spécifique pour null, et le fait qu'il puisse entrer en collision avec un élément d'une énumération, est un problème tellement insignifiant qu'il ne vaut pas le papier électronique que nous y consacrons. Il s'agit d'une question sans importance, d'un problème de performance nul (ou aussi proche de zéro qu'on puisse l'être).
1 votes
En ce qui concerne la première partie, nous notons également que, sans surprise,
System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(null)
donne également0
.0 votes
Cette question m'a ouvert les yeux... Je ne retournerai plus jamais 0 pour les codes de hachage nuls. Considérant que 0 est un hash assez commun pour int 0, bool false, nulls, enum default value etc. Performance performance performance ! ! ! Je suis méchant de toute façon...
0 votes
Cependant, je vois, avec
string
que, bien que lesEqualityComparer<string>.Default.GetHashCode(null)
utilise zéro comme hachage pour la chaîne de caractères nulle, des choses commeStringComparer.Ordinal.GetHashCode(null)
lancerArgumentNullException
. Il s'agit donc d'un cas particulier.0 votes
@JeppeStigNielsen : Les collisions sur le hachage zéro ne sont pas un problème dans une table de hachage de type chain-bucket, en particulier lorsque les comparaisons impliquant l'un des éléments seront de toute façon peu coûteuses. Un problème plus important avec les hachages zéro serait le code qui essaie de mettre en cache les valeurs de hachage mais qui ne peut pas mettre en cache le zéro. Pour cette raison, je suggère que les fonctions de hachage renvoient bien zéro lorsqu'elles peuvent le faire rapidement, mais que les fonctions de hachage plus lentes devraient essayer de l'éviter.