¿Cómo implementar mejor Equals para tipos personalizados?

Digamos para una clase Point2, y lo siguiente es igual a:

public override bool Equals ( object obj ) public bool Equals ( Point2 obj ) 

Este es el que se muestra en el Effective C # 3:

 public override bool Equals ( object obj ) { // STEP 1: Check for null if ( obj == null ) { return false; } // STEP 3: equivalent data types if ( this.GetType ( ) != obj.GetType ( ) ) { return false; } return Equals ( ( Point2 ) obj ); } public bool Equals ( Point2 obj ) { // STEP 1: Check for null if nullable (eg, a reference type) if ( obj == null ) { return false; } // STEP 2: Check for ReferenceEquals if this is a reference type if ( ReferenceEquals ( this, obj ) ) { return true; } // STEP 4: Possibly check for equivalent hash codes if ( this.GetHashCode ( ) != obj.GetHashCode ( ) ) { return false; } // STEP 5: Check base.Equals if base overrides Equals() System.Diagnostics.Debug.Assert ( base.GetType ( ) != typeof ( object ) ); if ( !base.Equals ( obj ) ) { return false; } // STEP 6: Compare identifying fields for equality. return ( ( this.X.Equals ( obj.X ) ) && ( this.Y.Equals ( obj.Y ) ) ); } 

Hay un conjunto completo de directrices en MSDN también. Deberías leerlos bien, es complicado e importante.

Algunos puntos que encontré más útiles:

  • Los tipos de valor no tienen identidad, por lo que en un struct Point generalmente se comparará un miembro por miembro.

  • Los tipos de referencia generalmente tienen identidad y, por lo tanto, la prueba de Igual generalmente se detiene en ReferenceEquals (por defecto, no es necesario anular). Pero existen excepciones, como la cadena y su class Point2 , donde un objeto no tiene una identidad útil y luego anula los miembros de Igualdad para proporcionar su propia semántica. En esa situación, siga las pautas para pasar primero por los casos nulo y de otro tipo.

  • Y hay buenas razones para mantener GethashCode() y operator== en sincronía también.

En el que toma un obj, si el tipo de obj es Point2, llame al tipo específico Equals. Dentro del tipo Igualitario específico, asegúrese de que todos los miembros tengan el mismo valor.

 public override bool Equals ( object obj ) { return Equals(obj as Point2); } public bool Equals ( Point2 obj ) { return obj != null && obj.X == this.X && obj.Y == this.Y ... // Or whatever you think qualifies as the objects being equal. } 

Probablemente también deba sobrescribir GetHashCode para asegurarse de que los objetos que son “iguales” tengan el mismo código hash.

La técnica que he usado que me ha funcionado es la siguiente. Tenga en cuenta que solo estoy comparando en base a una sola propiedad (Id) en lugar de dos valores. Ajustar según sea necesario

 using System; namespace MyNameSpace { public class DomainEntity { public virtual int Id { get; set; } public override bool Equals(object other) { return Equals(other as DomainEntity); } public virtual bool Equals(DomainEntity other) { if (other == null) { return false; } if (object.ReferenceEquals(this, other)) { return true; } return this.Id == other.Id; } public override int GetHashCode() { return this.Id; } public static bool operator ==(DomainEntity item1, DomainEntity item2) { if (object.ReferenceEquals(item1, item2)) { return true; } if ((object)item1 == null || (object)item2 == null) { return false; } return item1.Id == item2.Id; } public static bool operator !=(DomainEntity item1, DomainEntity item2) { return !(item1 == item2); } } } 
  • Defina lo que significa la identidad … si es la identidad de referencia, entonces las equivalentes heredadas predeterminadas funcionarán.
  • Si un tipo de valor (y por lo tanto, valor de identidad) debe definir.
  • Si es un tipo de clase, pero tiene una semántica de valores, entonces defina.

Es probable que desee anular Igual (objeto) y definir Igual (MiTipo) porque este último evita el boxeo. Y anula el operador de igualdad.

El libro .NET Framework Guidelines (2nd ed) tiene más cobertura.

Lie Daniel L dijo:

 public override bool Equals(object obj) { Point2 point = obj as Point2; // Point2? if Point2 is a struct return point != null && this.Equals(point); } public bool Equals(Point2 point) { ... } 

Leves variantes de formularios ya publicados por varios otros …

 using System; ... public override bool Equals ( object obj ) { return Equals(obj as SomeClass); } public bool Equals ( SomeClass someInstance ) { return Object.ReferenceEquals( this, someInstance ) || ( !Object.ReferenceEquals( someInstance, null ) && this.Value == someInstance.Value ); } public static bool operator ==( SomeClass lhs, SomeClass rhs ) { if( Object.ReferenceEquals( lhs, null ) ) { return Object.ReferenceEquals( rhs, null ); } return lhs.Equals( rhs ); //OR return Object.ReferenceEquals( lhs, rhs ) || ( !Object.ReferenceEquals( lhs, null ) && !Object.ReferenceEquals( rhs, null ) && lhs.Value == rhs.Value ); } public static bool operator !=( SomeClass lhs, SomeClass rhs ) { return !( lhs == rhs ); // OR return ( Object.ReferenceEquals( lhs, null ) || !lhs.Equals( rhs ) ) && !Object.ReferenceEquals( lhs, rhs ); } 

Intentando encontrar una forma de implementar operador == usando Igual para evitar la duplicación de la lógica de comparación de valores … sin ninguna prueba redundante (llamadas de ReferenceEquals con los mismos parámetros) o pruebas innecesarias (esto no puede ser nulo en la instancia.Equals método) y sin condicionales explícitos (“ifs”). Más de una idea más que cualquier cosa útil.

Lo más cerca que puedo pensar es esto, pero parece que debería ser posible sin un método adicional 🙂

 public bool Equals ( SomeClass someInstance ) { return Object.ReferenceEquals( this, someInstance ) || (!Object.ReferenceEquals( someInstance, null ) && EqualsNonNullInstance( someInstance ); } public static bool operator ==( SomeClass lhs, SomeClass rhs ) { return Object.ReferenceEquals( lhs, rhs ) || ( !Object.ReferenceEquals( lhs, null ) && !Object.ReferenceEquals( rhs, null ) && lhs.EqualsNonNullInstance( rhs ) ); } //super fragile method which returns logical non-sense protected virtual bool EqualsNonNullInstance ( SomeClass someInstance ) { //In practice this would be a more complex method... return this.Value == someInstance.Value; } 

Recordando cuán tedioso y propenso a los errores es todo esto (estoy casi seguro de que hay un error en el código anterior … que todavía apesta porque ¿quién quiere subclasificar un Tipo simplemente para simplificar las verificaciones de igualdad?), En adelante creo que Solo crearé algunos métodos estáticos que manejen todas las comprobaciones nulas y acepten un delegado o requieran una interfaz para realizar la comparación de valores (la única parte que realmente cambia Tipo a Tipo).

Sería fantástico si pudiéramos agregar atributos en los campos / propiedades / métodos que necesitan ser comparados y dejar que el comstackdor / tiempo de ejecución maneje todo el tedio.

También asegúrese de que los valores de GetHashCode () sean iguales para todas las instancias en las cuales .Equals (object) devuelve true o crazy shit.

También hay un plugin Fody Equals.Fody que genera Equals () y GetHashCode () automáticamente

 public override bool Equals ( object obj ) { // struct return obj is Point2 && Equals ( ( Point2 ) value ); // class //return Equals ( obj as Point2 ); } public bool Equals ( Point2 obj )