2009-02-19 8 views
25

Say para una clase Point2, y la siguiente es igual a:¿Cuál es la mejor manera de implementar Equals para tipos personalizados?

public override bool Equals (object obj) 

public bool Equals (Point2 obj) 

Esta es la que se muestra en la efectiva 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 (e.g., 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))); 
} 

Respuesta

18

Hay toda una serie de guidelines on MSDN también. Deberías leerlos bien, es complicado e importante.

Unos pocos puntos que me pareció más útil:

  • tipos de valor no tienen identidad, por lo que en un struct Point lo normal es hacer un miembro por miembro comparar.

  • Los tipos de referencia generalmente tienen identidad y, por lo tanto, la prueba de Igual generalmente se detiene en ReferenceEquals (el valor predeterminado, no es necesario anular). Pero hay excepciones, como string y su class Point2, donde un objeto no tiene una identidad útil y luego sobrescribe 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== sincronizados también.

28

En la que lleva 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 debería reemplazar GetHashCode así para asegurarse de que los objetos que son "iguales" tienen el mismo código hash.

+0

Wont este explotar si objeto 'obj' es una estructura? – row1

+0

@ row1 No. La estructura será encuadrada, y la estructura encuadrada tendrá como resultado 'null' cuando se evalúe el operador' as'. Si el tipo definido es una estructura, entonces necesitarías hacer un reparto explícito en lugar de usar 'as' (o usar la versión que puede agregar nulos de la estructura). También eliminaría las comprobaciones nulas si fuera una estructura no nulable. – Servy

+0

Woow muy simple y consise Implementación, me gusta, gracias. –

2
  • Define lo que significa la identidad ... si la identidad de referencia, entonces los valores por defecto heredados por defecto funcionarán.
  • Si es necesario definir un tipo de valor (y, por tanto, identidad de valor).
  • Si es un tipo de clase, pero tiene una semántica de valores, defina.

probable es que quieren es igual tanto anulación (objeto) y definir Iguales (MyType) ya que este último evita el boxeo. Y anula el operador de igualdad.

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

0

Lie L Daniel 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) { 
    ... 
} 
-1
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) 
7

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. Ajuste 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); 
     } 
    } 
} 
0

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

Tratando de encontrar una manera de poner en práctica operador == usando Iguales para evitar la duplicación de la lógica de comparación de valor ... sin ningún tipo de pruebas redundantes (ReferenceEquals llamadas w/los mismos parámetros) o pruebas innecesarias (esto no puede ser nulo en el método instancia.Equals) y sin condicionales explícitos ("ifs"). Más de una idea más que cualquier cosa útil.

más cercano que puedo pensar es esto, pero siente como tiene que 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 lo tedioso y propenso a errores que todo esto es (estoy casi seguro hay un error en el código anterior ... que todavía chupa porque ¿quién quiere una subclase Tipo acaba de hacer comprobaciones de igualdad un poco más sencillo?), en adelante creo que voy a crear algunos métodos estáticos que se encargan de todos los cheques nulos y aceptar un delegado o requerir e interfaz para realizar la comparación de valores (la única parte que realmente cambia Tipo a Tipo).

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

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

Cuestiones relacionadas