Quelle est la meilleure stratégie pour Equals et GetHashCode?

Je travaillais avec un modèle de domaine et je réfléchissais aux différentes manières dont nous devons implémenter ces deux méthodes dans .NET. Quelle est votre stratégie préférée?

Ceci est ma mise en œuvre actuelle:

public override bool Equals(object obj) { var newObj = obj as MyClass; if (null != newObj) { return this.GetHashCode() == newObj.GetHashCode(); } else { return base.Equals(obj); } } //Since this is an entity I can use it´s Id //When I don´t have an Id I usually make a composite key of the properties public override int GetHashCode() { return Ssortingng.Format("MyClass{0}", this.Id.ToSsortingng()).GetHashCode(); } 

En supposant que les instances soient égales parce que les codes de hachage sont égaux, c’est faux.

Je suppose que votre implémentation de GetHashCode est OK, mais j’utilise habituellement des choses similaires à celles-ci:

 public override int GetHashCode() { return object1.GetHashCode ^ intValue1 ^ (intValue2 << 16); } 

La conception axée sur le domaine fait la distinction entre entités et objects de valeur . C’est une bonne distinction à observer car elle guide la manière dont vous implémentez Equals.

Les entités sont égales si leurs identifiants sont égaux.

Les objects de valeur sont égaux si tous leurs éléments constitutifs (importants) sont égaux les uns aux autres.

Dans tous les cas, l’implémentation de GetHashCode doit se baser sur les mêmes valeurs que celles utilisées pour déterminer l’égalité. En d’autres termes, pour les entités, le code de hachage doit être calculé directement à partir de l’ID, tandis que pour les objects de valeur, il doit être calculé à partir de toutes les valeurs constituantes.

Aucune des réponses ici ne me convient vraiment. Comme vous avez déjà dit que vous ne pouvez pas utiliser Id pour l’égalité et que vous devez utiliser un ensemble de propriétés, voici une meilleure façon de le faire. Remarque: je ne considère pas cela comme la meilleure façon de mettre en œuvre Equals et GetHashCode . C’est une meilleure version du code de l’OP.

 public override bool Equals(object obj) { var myClass = obj as MyClass; if (null != myClass) { // Order these by the most different first. // That is, whatever value is most selective, and the fewest // instances have the same value, put that first. return this.Id == myClass.Id && this.Name == myClass.Name && this.Quantity == myClass.Quantity && this.Color == myClass.Color; } else { // Not sure this makes sense! return base.Equals(obj); } } public override int GetHashCode() { int hash = 19; unchecked { // allow "wrap around" in the int hash = hash * 31 + this.Id; // assuming integer hash = hash * 31 + this.Name.GetHashCode(); hash = hash * 31 + this.Quantity; // again assuming integer hash = hash * 31 + this.Color.GetHashCode(); } return hash; } 

Voir cette réponse de Jon Skeet pour une partie du raisonnement derrière cela. L’utilisation de xor n’est pas une bonne solution car plusieurs ensembles de données peuvent aboutir au même hachage. Cette méthode de bouclage avec des nombres premiers (les valeurs de départ de 19 et 31 ci-dessus, ou d’autres valeurs que vous choisissez) permet un meilleur travail de segmentation en “compartiments” ayant chacun peu de collisions.

Si l’une de vos valeurs peut être nulle, je vous encourage à réfléchir attentivement à la manière dont elles devraient être comparées. Vous pouvez éventuellement utiliser l’évaluation nulle du court-circuit et l’opérateur de coalescence nul. Mais assurez-vous que si les valeurs NULL doivent être équivalentes, vous affectez différents codes de hachage aux différentes propriétés Nullable, lorsqu’elles sont nulles.

De plus, je ne suis pas convaincu que votre implémentation d’ Equals un sens. Lorsque deux objects sont comparés pour égalité, leurs valeurs GetHashCode sont d’abord comparées. La méthode Equals exécutée uniquement si celles-ci sont différentes (ainsi, si deux objects dont la valeur est identique à celle-ci sont différents, cela sera détecté). Étant donné que votre implémentation GetHashCode ne fait pas référence à la base , cela n’a aucun sens pour votre méthode Equals de le faire.

Les codes de hachage peuvent entrer en collision, je ne pense donc pas qu’ils constituent un bon moyen de comparer l’égalité. Vous devez comparer les valeurs sous-jacentes qui rendent les objects “égaux”. Voir la réponse de @Jon Skeet à cette question: Quel est le meilleur algorithme pour un System.Object.GetHashCode surchargé? pour une meilleure implémentation de GetHashCode si votre égalité englobe plusieurs propriétés. S’il ne s’agit que d’une seule propriété, vous pouvez simplement réutiliser son hashcode.

Je suis tombé sur cette vieille question et, à mon humble avis, je n’ai trouvé aucune réponse claire et simple à la question initiale formulée par @tucaz.

Je peux être d’accord avec de nombreuses considérations partagées ci-dessus (ou ci-dessous: D) mais le «point question» a été oublié (je pense).

À condition que:

  • L’égalité est requirejse pour les entités
  • Les objects d’entité peuvent être considérés comme égaux s’ils mappent la même entité, sinon ils font référence à la même «clé d’entité»
  • L’exemple montré par @tucaz mentionne juste un «Id» (voir le GetHashCode () sur-implémenté)… sans parler du buggy Equals (…)

Je peux deviner qu’une implémentation simple pourrait être:

 public class MyEntity: IEquatable { int Id; public MyEntity(int id){ Id = id; } public override bool Equals(object obj) => Equals(obj as MyEntity); public bool Equals(MyEntity obj) => obj != null && Id == obj.Id; public override int GetHashCode() => Id; } 

C’est tout!