Como melhor implementar Equals para tipos personalizados?

Diga para uma class Point2 e o seguinte é igual a:

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

Este é o que é mostrado no C # 3 efetivo:

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

Há todo um conjunto de diretrizes no MSDN também. Você deve lê-los bem, é ao mesmo tempo complicado e importante.

Alguns pontos que achei mais úteis:

  • Tipos de valor não possuem Identidade, portanto, em um struct Point você normalmente fará um membro por comparação de membro.

  • Tipos de referência geralmente têm identidade e, portanto, o teste Equals geralmente pára em ReferenceEquals (o padrão, não é necessário replace). Mas há exceções, como string e sua class Point2 , em que um object não possui uma identidade útil e, em seguida, você substitui os membros Equality para fornecer sua própria semântica. Nessa situação, siga as diretrizes para passar primeiro pelos casos nulos e de outros tipos.

  • E há boas razões para manter o GethashCode() e o operator== em sincronia também.

No que toma um obj, se o tipo de obj é Point2, chame o tipo específico Equals. Dentro do tipo específico Equals, certifique-se de que todos os membros tenham o mesmo 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. } 

Você provavelmente deve replace o GetHashCode também para garantir que os objects “iguais” tenham o mesmo código hash.

A técnica que usei e que funcionou para mim é a seguinte. Note, eu estou apenas comparando com base em uma única propriedade (Id) em vez de dois valores. Ajuste conforme necessário

 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 o que significa a identidade. Se a identidade de referência, os iguais herdados padrão funcionarão.
  • Se um tipo de valor (e, portanto, identidade de valor), você precisa definir.
  • Se um tipo de class, mas tem semântica de valor, então defina.

Provavelmente você quer tanto replace Equals (object) e definir Equals (MyType), porque o último evita o boxe. E replace o operador de igualdade.

O guia de diretrizes do .NET Framework (2nd ed) tem mais cobertura.

Mentira Daniel L disse:

 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) { ... } 

Ligeiras variantes de formulários já postados por vários outros …

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

Tentando encontrar uma maneira de implementar o operador == usando Equals para evitar a duplicação da lógica de comparação de valores … sem testes redundantes (chamadas de ReferenceEquals com os mesmos parâmetros) ou testes desnecessários (isso não pode ser nulo na instância.Equals método) e sem quaisquer condicionais explícitos (“ifs”). Mais um quebra-cabeças do que qualquer coisa útil.

Mais próximo que eu posso pensar é isso, mas parece que deve ser possível sem um método extra 🙂

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

Lembrando como é tedioso e propenso a erros, tudo isso é (eu tenho quase certeza de que há um erro no código acima … o que ainda é ruim porque quem quer subclassificar um Type apenas para tornar as verificações de igualdade um pouco mais simples?), Vamos apenas criar alguns methods estáticos que manipulam todas as verificações nulas e aceitar um delegado ou exigir e interface para realizar a comparação de valores (a única parte que realmente muda de tipo para tipo).

Seria ótimo se pudéssemos adicionar atributos aos campos / propriedades / methods que precisam ser comparados e deixar o compilador / runtime lidar com todo o tédio.

Certifique-se também de que os valores de GetHashCode () sejam iguais para todas as instâncias nas quais .Equals (object) retorna true ou merda maluca pode acontecer.

Há também um plugin Fodies Equals.Fody que gera Equals () e GetHashCode () automaticamente

 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 )