¿Cuál es la mejor estrategia para Equals y GetHashCode?

Estoy trabajando con un modelo de dominio y estaba pensando en las diversas formas en que tenemos que implementar estos dos métodos en .NET. ¿Cuál es tu estrategia preferida?

Esta es mi implementación actual:

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 String.Format("MyClass{0}", this.Id.ToString()).GetHashCode(); } 

Asumir que las instancias son iguales porque los códigos hash son iguales es incorrecto.

Supongo que su implementación de GetHashCode está bien, pero suelo usar cosas similares a esto:

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

Domain-Driven Design hace la distinción entre Entities y Value Objects . Esta es una buena distinción para observar ya que guía cómo implementar Equals.

Las entidades son iguales si sus identificaciones son iguales entre sí.

Los objetos de valor son iguales si todos sus elementos constituyentes (importantes) son iguales entre sí.

En cualquier caso, la implementación de GetHashCode debe basarse en los mismos valores que se utilizan para determinar la igualdad. En otras palabras, para las entidades, el código hash debe calcularse directamente desde el ID, mientras que para los objetos de valor se debe calcular a partir de todos los valores constituyentes.

Ninguna de las respuestas aquí realmente me golpea. Como ya ha dicho que no puede usar Id para la igualdad, y necesita usar un conjunto de propiedades, esta es una mejor manera de hacerlo. Nota: No considero que esta sea la mejor manera de implementar Equals y GetHashCode . Esta es una mejor versión del código 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; } 

Vea esta respuesta de Jon Skeet para algunos de los razonamientos detrás de esto. Usar xor no es bueno porque varios conjuntos de datos pueden terminar dando como resultado el mismo hash. Este método envolvente con primos (los valores iniciales de 19 y 31, u otros valores que elija) hace un mejor trabajo de segmentación en “cubos” que tienen pocas colisiones cada uno.

Si alguno de tus valores puede ser nulo, te animo a que pienses detenidamente sobre cómo deben comparar. Podría usar la evaluación nula de cortocircuito y quizás el operador de fusión nulo. Pero asegúrese de que si los valores nulos se pueden comparar como iguales, asigne diferentes códigos hash a las diferentes propiedades que aceptan valores nulos cuando son nulos.

Además, no estoy convencido de que su implementación Equals tenga sentido. Cuando se comparan dos objetos para la igualdad, primero se comparan los valores de GetHashCode . Solo si esos son diferentes se ejecuta el método Equals (de modo que si dos objetos que tienen hash con el mismo valor son diferentes, esto se detectará). Como la implementación de GetHashCode no hace referencia a la base , no tiene sentido que su método Equals lo haga.

Los Hashcodes pueden colisionar, así que no creo que sean una buena forma de comparar la igualdad. En su lugar, debe comparar los valores subyacentes que hacen que los objetos sean “iguales”. Ver la respuesta de @Jon Skeet a esta pregunta: ¿Cuál es el mejor algoritmo para un System.Object.GetHashCode reemplazado? para una mejor implementación de GetHashCode si su igualdad abarca varias propiedades. Si se trata de una sola propiedad, puede reutilizar su código hash.

Me encontré con esta vieja pregunta y, en mi humilde opinión, no encontré ninguna respuesta clara y simple, formulé la pregunta original formulada por @tucaz.

Puedo estar de acuerdo con muchas consideraciones compartidas arriba (o debajo: D) pero el “Punto de Pregunta” fue extrañado (creo).

Siempre que:

  • La igualdad es requerida para las entidades
  • Los objetos de la entidad se pueden considerar iguales si mapean la misma entidad, id est a que se refieren a la misma «Clave de la entidad»
  • El ejemplo mostrado por @tucaz solo menciona un «Id» (ver el GetHashCode () demasiado implementado … sin mencionar el buggy Equals (…)

Adivino que una implementación directa podría ser:

 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; } 

¡Eso es todo!