Comment c# calcule-t-il le code de hachage d'un objet?
cette question sort de la discussion sur tuples .
j'ai commencé à penser au code de hachage qu'un tuple devrait avoir. Que faire si nous acceptons KeyValuePair class comme un tuple? Il ne supplante pas la méthode GetHashCode (), donc probablement il ne sera pas au courant des codes de hachage de ses "enfants"... Donc, run-time appellera Object.GetHashCode (), qui n'est pas conscient de la structure de l'objet réel.
alors nous pouvons faire deux les instances d'un type de référence, qui sont en fait égales, en raison de la surcharge de GetHashCode() et D'Equals(). Et les utiliser comme " enfants "dans tuples pour" tricher " le dictionnaire.
mais ça ne marche pas! Run-time trouve en quelque sorte la structure de notre tuple et appelle le GetHashCode surchargé de notre classe!
comment ça marche? Quelle est l'analyse faite par l'Objet.GetHashCode ()?
peut-il affecter la performance dans certains scénario, quand on utilise des clés compliquées? (scénario probablement impossible... mais encore)
considérez ce code comme un exemple:
namespace csharp_tricks
{
class Program
{
class MyClass
{
int keyValue;
int someInfo;
public MyClass(int key, int info)
{
keyValue = key;
someInfo = info;
}
public override bool Equals(object obj)
{
MyClass other = obj as MyClass;
if (other == null) return false;
return keyValue.Equals(other.keyValue);
}
public override int GetHashCode()
{
return keyValue.GetHashCode();
}
}
static void Main(string[] args)
{
Dictionary<object, object> dict = new Dictionary<object, object>();
dict.Add(new KeyValuePair<MyClass,object>(new MyClass(1, 1), 1), 1);
//here we get the exception -- an item with the same key was already added
//but how did it figure out the hash code?
dict.Add(new KeyValuePair<MyClass,object>(new MyClass(1, 2), 1), 1);
return;
}
}
}
Update je pense que j'ai trouvé une explication pour cela comme indiqué ci-dessous dans ma réponse. Les principaux résultats sont:
- faites attention avec vos clés et leurs codes de hachage: -)
- pour les clés de dictionnaires compliquées vous devez override Equals() et GetHashCode() correctement.
6 réponses
Ne surchargez pas GetHashcode () et Equals () sur les classes modifiables, ne le surchargez que sur les classes ou structures immuables, sinon si vous modifiez un objet utilisé comme clé la table de hachage ne fonctionnera plus correctement (vous ne pourrez plus récupérer la valeur associée à la clé après que l'objet clé a été modifié)
aussi les tables de hachage n'utilisent pas de hashcodes pour identifier les objets clés qu'ils utilisent eux-mêmes comme identificateurs, il n'est pas nécessaire que toutes les clés qui sont utilisées pour ajouter des entrées dans une table de hachage de retour différents hashcodes, mais il est recommandé qu'ils font, d'autre rendement s'en ressent grandement.
Voici les implémentations appropriées de hachage et d'égalité pour le quad tuple (contient 4 composants tuple à l'intérieur). Ce code assure une utilisation correcte de ce tuple spécifique dans les HashSets et les dictionnaires.
plus sur le sujet (y compris le code source) ici .
Note usage du unchecked mot-clé (pour éviter les débordements) et lancer NullReferenceException si obj est nul (comme requis par la méthode de base)
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
throw new NullReferenceException("obj is null");
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != typeof (Quad<T1, T2, T3, T4>)) return false;
return Equals((Quad<T1, T2, T3, T4>) obj);
}
public bool Equals(Quad<T1, T2, T3, T4> obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
return Equals(obj.Item1, Item1)
&& Equals(obj.Item2, Item2)
&& Equals(obj.Item3, Item3)
&& Equals(obj.Item4, Item4);
}
public override int GetHashCode()
{
unchecked
{
int result = Item1.GetHashCode();
result = (result*397) ^ Item2.GetHashCode();
result = (result*397) ^ Item3.GetHashCode();
result = (result*397) ^ Item4.GetHashCode();
return result;
}
}
public static bool operator ==(Quad<T1, T2, T3, T4> left, Quad<T1, T2, T3, T4> right)
{
return Equals(left, right);
}
public static bool operator !=(Quad<T1, T2, T3, T4> left, Quad<T1, T2, T3, T4> right)
{
return !Equals(left, right);
}
découvrez ce post par Brad Abrams et aussi le commentaire de Brian Grunkemeyer pour plus d'information sur la façon de l'objet.GetHashCode fonctionne. Aussi, jetez un oeil au premier commentaire sur le blog D'Ayande post . Je ne sais pas si les versions actuelles du Framework suivent toujours ces règles ou si elles ont vraiment changé comme Brad l'a laissé entendre.
il semble que j'ai un indice maintenant.
je pensais que KeyValuePair est un type de référence, mais ce n'est pas, c'est une structure. Et donc il utilise ValueType.Méthode GetHashCode (). MSDN pour cela dit: "un ou plusieurs champs du type dérivé est utilisé pour calculer la valeur de retour".
si vous voulez prendre un vrai type de référence comme un" tuple-provider " vous tricher le dictionnaire (ou vous-même...).
using System.Collections.Generic;
namespace csharp_tricks
{
class Program
{
class MyClass
{
int keyValue;
int someInfo;
public MyClass(int key, int info)
{
keyValue = key;
someInfo = info;
}
public override bool Equals(object obj)
{
MyClass other = obj as MyClass;
if (other == null) return false;
return keyValue.Equals(other.keyValue);
}
public override int GetHashCode()
{
return keyValue.GetHashCode();
}
}
class Pair<T, R>
{
public T First { get; set; }
public R Second { get; set; }
}
static void Main(string[] args)
{
var dict = new Dictionary<Pair<int, MyClass>, object>();
dict.Add(new Pair<int, MyClass>() { First = 1, Second = new MyClass(1, 2) }, 1);
//this is a pair of the same values as previous! but... no exception this time...
dict.Add(new Pair<int, MyClass>() { First = 1, Second = new MyClass(1, 3) }, 1);
return;
}
}
}
Je n'ai plus la référence du livre, et je vais devoir la trouver juste pour confirmer, mais je pensais que le hachage de base par défaut vient de hashé ensemble tous les membres de votre objet. Il a eu accès à eux à cause de la façon dont le CLR fonctionnait, donc ce n'était pas quelque chose que vous pouviez écrire aussi bien qu'eux.
C'est complètement de mémoire de quelque chose que j'ai lu brièvement alors prenez-le pour ce que vous voulez.
Edit: Le livre a été à l'Intérieur de C# à partir de MS de Presse. Celui avec la lame de scie sur la couverture. L'auteur a passé beaucoup de temps à expliquer comment les choses étaient mises en œuvre dans le CLR, comment le langage traduit en MSIL, ect. ect. Si vous pouvez trouver le livre c'est pas une mauvaise lecture.
Edit: Forme le lien prévu, il ressemble à
objet.GetHashCode () utilise un champ interne dans le Système.La classe d'objet pour générer la valeur de hachage. Chacun objet créé est assigné une clé d'objet unique, stockée comme un entier, quand il est créé. Ces touches commencent à 1 et augmentent à chaque fois un nouvel objet de n'importe quel type est créé.
Hmm je suppose que je dois écrire quelques-uns de mes propres codes de hachage, si je m'attends à utiliser des objets comme des clés de hachage.
alors, probablement, il ne sera pas au courant des codes de hachage de ses "enfants".
votre exemple semble prouver le contraire :-) le code de hachage pour la clé MyClass
et la valeur 1
est le même pour les deux KeyValuePair
. L'implémentation de KeyValuePair doit utiliser à la fois ses Key
et Value
pour son propre code de hachage
en montant, la classe du dictionnaire veut des Clés uniques. C'est à l'aide de la hashcode fourni par chaque clé pour comprendre les choses. Rappelez-vous que le runtime n'appelle pas Object.GetHashCode()
, mais il appelle l'implémentation GetHashCode() fournie par l'instance que vous lui donnez.
Considérer un cas plus complexe:
public class HappyClass
{
enum TheUnit
{
Points,
Picas,
Inches
}
class MyDistanceClass
{
int distance;
TheUnit units;
public MyDistanceClass(int theDistance, TheUnit unit)
{
distance = theDistance;
units = unit;
}
public static int ConvertDistance(int oldDistance, TheUnit oldUnit, TheUnit newUnit)
{
// insert real unit conversion code here :-)
return oldDistance * 100;
}
/// <summary>
/// Figure out if we are equal distance, converting into the same units of measurement if we have to
/// </summary>
/// <param name="obj">the other guy</param>
/// <returns>true if we are the same distance</returns>
public override bool Equals(object obj)
{
MyDistanceClass other = obj as MyDistanceClass;
if (other == null) return false;
if (other.units != this.units)
{
int newDistance = MyDistanceClass.ConvertDistance(other.distance, other.units, this.units);
return distance.Equals(newDistance);
}
else
{
return distance.Equals(other.distance);
}
}
public override int GetHashCode()
{
// even if the distance is equal in spite of the different units, the objects are not
return distance.GetHashCode() * units.GetHashCode();
}
}
static void Main(string[] args)
{
// these are the same distance... 72 points = 1 inch
MyDistanceClass distPoint = new MyDistanceClass(72, TheUnit.Points);
MyDistanceClass distInch = new MyDistanceClass(1, TheUnit.Inch);
Debug.Assert(distPoint.Equals(distInch), "these should be true!");
Debug.Assert(distPoint.GetHashCode() != distInch.GetHashCode(), "But yet they are fundimentally different values");
Dictionary<object, object> dict = new Dictionary<object, object>();
dict.Add(new KeyValuePair<MyDistanceClass, object>(distPoint, 1), 1);
//this should not barf
dict.Add(new KeyValuePair<MyDistanceClass, object>(distInch, 1), 1);
return;
}
}
en gros... dans le cas de mon exemple, vous voudriez que deux objets qui sont à la même distance retournent "true" pour égal, mais tout de même renvoient des codes de hachage différents.