How to: Definir o Valor de igualdade para um Tipo (guia de programação C#)
Quando você define uma classe ou estrutura, você decidir se ele faz sentido criar uma definição personalizada de igualdade de valor (ou equivalência) para o tipo. Normalmente, você implementar a igualdade do valor quando objetos do tipo devem ser adicionados a uma coleção de algum tipo, ou quando sua finalidade principal é armazenar um conjunto de campos ou propriedades. Você pode basear sua definição de igualdade do valor em uma comparação de todos os campos e propriedades no tipo ou você pode basear a definição de um subconjunto. Mas, em ambos os casos e em classes e estruturas, sua implementação deve seguir as garantias de equivalência de cinco:
x.Equals(x) retorna true. é chamado propriedade reflexivo.
x.Equals(y) retorna o mesmo valor de y.Equals(x). Isso se chama a propriedade simétrica.
Se (x.Equals(y) & & y.Equals(z)) retorna true, em seguida, x.Equals(z) returns true. Isso se chama a propriedade transitiva.
Invocações sucessivas de x.Equalso mesmo valor, desde que os objetos referenciados pelo x e y não são modificados de retorno de (y).
x.Equals(nulo) retorna false. No entanto, nulo.Equals(null) lança uma exceção; ele não obedecer a regra número dois acima.
Qualquer estrutura que você definir já tem uma implementação padrão de igualdade do valor que ele herda o System.ValueType Substituir da Object.Equals(Object) método. Essa implementação usa a reflexão para examinar todos os campos públicos e não-públicos e propriedades do tipo. Embora essa implementação produz resultados corretos, é relativamente lenta em comparação com uma implementação personalizada que você escrever especificamente para o tipo.
Os detalhes da implementação de igualdade de valor são diferentes das classes e estruturas. No entanto, as classes e estruturas requerem as mesmas etapas básicas para a implementação de igualdade:
Substituir o virtual Object.Equals(Object) método. Na maioria dos casos, sua implementação de bool Equals( object obj ) , devem simplesmente chamar específicos do tipo Equals método é a implementação da System.IEquatable<T> interface. (Consulte a etapa 2).
Implementar a System.IEquatable<T> interface, fornecendo um tipo específico Equals método. Isso é onde a comparação de equivalência real é realizada. Por exemplo, você pode decidir definir igualdade comparando-se apenas um ou dois campos seu tipo. Não jogue exceções a partir de Equals. Classes: Esse método deve examinar apenas os campos são declarados na classe. Ela deve chamar base.Equals para examinar os campos que estão com a classe de base. (Não faça isso se o tipo herda diretamente da Object, porque o Object a implementação de Object.Equals(Object) executa uma verificação de igualdade de referência.)
Opcional, mas recomendável: Sobrecarregar o = = e ! = operadores.
Substituir Object.GetHashCode para que os dois objetos que possuem a igualdade do valor de produzir o mesmo código de hash.
Opcional: Para suportar as definições para "maior que" ou "less than", implementar a IComparable<T> para o seu tipo de interface e também sobrecarregar o < = e > = operadores.
O primeiro exemplo a segue mostra uma implementação de classe. O segundo exemplo mostra uma implementação de struct.
Exemplo
O exemplo a seguir mostra como implementar a igualdade do valor em uma classe (tipo de referência).
namespace ValueEquality
{
using System;
class TwoDPoint : IEquatable<TwoDPoint>
{
// Readonly auto-implemented properties.
public int X { get; private set; }
public int Y { get; private set; }
// Set the properties in the constructor.
public TwoDPoint(int x, int y)
{
if ((x < 1) || (x > 2000) || (y < 1) || (y > 2000))
throw new System.ArgumentException("Point must be in range 1 - 2000");
this.X = x;
this.Y = y;
}
public override bool Equals(object obj)
{
return this.Equals(obj as TwoDPoint);
}
public bool Equals(TwoDPoint p)
{
// If parameter is null, return false.
if (Object.ReferenceEquals(p, null))
{
return false;
}
// Optimization for a common success case.
if (Object.ReferenceEquals(this, p))
{
return true;
}
// If run-time types are not exactly the same, return false.
if (this.GetType() != p.GetType())
return false;
// Return true if the fields match.
// Note that the base class is not invoked because it is
// System.Object, which defines Equals as reference equality.
return (X == p.X) && (Y == p.Y);
}
public override int GetHashCode()
{
return X * 0x00010000 + Y;
}
public static bool operator ==(TwoDPoint lhs, TwoDPoint rhs)
{
// Check for null on left side.
if (Object.ReferenceEquals(lhs, null))
{
if (Object.ReferenceEquals(rhs, null))
{
// null == null = true.
return true;
}
// Only the left side is null.
return false;
}
// Equals handles case of null on right side.
return lhs.Equals(rhs);
}
public static bool operator !=(TwoDPoint lhs, TwoDPoint rhs)
{
return !(lhs == rhs);
}
}
// For the sake of simplicity, assume a ThreeDPoint IS a TwoDPoint.
class ThreeDPoint : TwoDPoint, IEquatable<ThreeDPoint>
{
public int Z { get; private set; }
public ThreeDPoint(int x, int y, int z)
: base(x, y)
{
if ((z < 1) || (z > 2000))
throw new System.ArgumentException("Point must be in range 1 - 2000");
this.Z = z;
}
public override bool Equals(object obj)
{
return this.Equals(obj as ThreeDPoint);
}
public bool Equals(ThreeDPoint p)
{
// If parameter is null, return false.
if (Object.ReferenceEquals(p, null))
{
return false;
}
// Optimization for a common success case.
if (Object.ReferenceEquals(this, p))
{
return true;
}
// Check properties that this class declares.
if (Z == p.Z)
{
// Let base class check its own fields
// and do the run-time type comparison.
return base.Equals((TwoDPoint)p);
}
else
return false;
}
public override int GetHashCode()
{
return (X * 0x100000) + (Y * 0x1000) + Z;
}
public static bool operator ==(ThreeDPoint lhs, ThreeDPoint rhs)
{
// Check for null.
if (Object.ReferenceEquals(lhs, null))
{
if (Object.ReferenceEquals(lhs, null))
{
// null == null = true.
return true;
}
// Only the left side is null.
return false;
}
// Equals handles the case of null on right side.
return lhs.Equals(rhs);
}
public static bool operator !=(ThreeDPoint lhs, ThreeDPoint rhs)
{
return !(lhs == rhs);
}
}
class Program
{
static void Main(string[] args)
{
ThreeDPoint pointA = new ThreeDPoint(3, 4, 5);
ThreeDPoint pointB = new ThreeDPoint(3, 4, 5);
ThreeDPoint pointC = null;
int i = 5;
Console.WriteLine("pointA.Equals(pointB) = {0}", pointA.Equals(pointB));
Console.WriteLine("pointA == pointB = {0}", pointA == pointB);
Console.WriteLine("null comparison = {0}", pointA.Equals(pointC));
Console.WriteLine("Compare to some other type = {0}", pointA.Equals(i));
TwoDPoint pointD = null;
TwoDPoint pointE = null;
Console.WriteLine("Two null TwoDPoints are equal: {0}", pointD == pointE);
pointE = new TwoDPoint(3, 4);
Console.WriteLine("(pointE == pointA) = {0}", pointE == pointA);
Console.WriteLine("(pointA == pointE) = {0}", pointA == pointE);
Console.WriteLine("(pointA != pointE) = {0}", pointA != pointE);
System.Collections.ArrayList list = new System.Collections.ArrayList();
list.Add(new ThreeDPoint(3, 4, 5));
Console.WriteLine("pointE.Equals(list[0]): {0}", pointE.Equals(list[0]));
// Keep the console window open in debug mode.
System.Console.WriteLine("Press any key to exit.");
System.Console.ReadKey();
}
}
/* Output:
pointA.Equals(pointB) = True
pointA == pointB = True
null comparison = False
Compare to some other type = False
Two null TwoDPoints are equal: True
(pointE == pointA) = False
(pointA == pointE) = False
(pointA != pointE) = True
pointE.Equals(list[0]): False
*/
}
Em classes (tipos de referência), a implementação do padrão de ambos Object.Equals(Object) métodos realiza uma comparação de igualdade de referência, não um valor igualdade seleção. Quando um implementador substitui o método virtual, o objetivo é dar a semântica de igualdade do valor.
O == e != operadores podem ser usados com classes, mesmo se a classe não sobrecarregar a eles. No entanto, o comportamento padrão é executar uma verificação de igualdade de referência. Em uma classe, se você sobrecarregar o Equals método, você deverá sobrecarregar o == e != operadores, mas não é necessária.
O exemplo a seguir mostra como implementar a igualdade do valor em uma estrutura (tipo de valor):
struct TwoDPoint : IEquatable<TwoDPoint>
{
// Read/write auto-implemented properties.
public int X { get; private set; }
public int Y { get; private set; }
public TwoDPoint(int x, int y)
: this()
{
X = x;
Y = x;
}
public override bool Equals(object obj)
{
if (obj is TwoDPoint)
{
return this.Equals((TwoDPoint)obj);
}
return false;
}
public bool Equals(TwoDPoint p)
{
return (X == p.X) && (Y == p.Y);
}
public override int GetHashCode()
{
return X ^ Y;
}
public static bool operator ==(TwoDPoint lhs, TwoDPoint rhs)
{
return lhs.Equals(rhs);
}
public static bool operator !=(TwoDPoint lhs, TwoDPoint rhs)
{
return !(lhs.Equals(rhs));
}
}
class Program
{
static void Main(string[] args)
{
TwoDPoint pointA = new TwoDPoint(3, 4);
TwoDPoint pointB = new TwoDPoint(3, 4);
int i = 5;
// Compare using virtual Equals, static Equals, and == and != operators.
// True:
Console.WriteLine("pointA.Equals(pointB) = {0}", pointA.Equals(pointB));
// True:
Console.WriteLine("pointA == pointB = {0}", pointA == pointB);
// True:
Console.WriteLine("Object.Equals(pointA, pointB) = {0}", Object.Equals(pointA, pointB));
// False:
Console.WriteLine("pointA.Equals(null) = {0}", pointA.Equals(null));
// False:
Console.WriteLine("(pointA == null) = {0}", pointA == null);
// True:
Console.WriteLine("(pointA != null) = {0}", pointA != null);
// False:
Console.WriteLine("pointA.Equals(i) = {0}", pointA.Equals(i));
// CS0019:
// Console.WriteLine("pointA == i = {0}", pointA == i);
// Compare unboxed to boxed.
System.Collections.ArrayList list = new System.Collections.ArrayList();
list.Add(new TwoDPoint(3, 4));
// True:
Console.WriteLine("pointE.Equals(list[0]): {0}", pointA.Equals(list[0]));
// Compare nullable to nullable and to non-nullable.
TwoDPoint? pointC = null;
TwoDPoint? pointD = null;
// False:
Console.WriteLine("pointA == (pointC = null) = {0}", pointA == pointC);
// True:
Console.WriteLine("pointC == pointD = {0}", pointC == pointD);
TwoDPoint temp = new TwoDPoint(3, 4);
pointC = temp;
// True:
Console.WriteLine("pointA == (pointC = 3,4) = {0}", pointA == pointC);
pointD = temp;
// True:
Console.WriteLine("pointD == (pointC = 3,4) = {0}", pointD == pointC);
// Keep the console window open in debug mode.
System.Console.WriteLine("Press any key to exit.");
System.Console.ReadKey();
}
}
/* Output:
pointA.Equals(pointB) = True
pointA == pointB = True
Object.Equals(pointA, pointB) = True
pointA.Equals(null) = False
(pointA == null) = False
(pointA != null) = True
pointA.Equals(i) = False
pointE.Equals(list[0]): True
pointA == (pointC = null) = False
pointC == pointD = True
pointA == (pointC = 3,4) = True
pointD == (pointC = 3,4) = True
*/
}
Para estruturas, a implementação padrão do Object.Equals(Object) (que é a versão substituída na System.ValueType) executa uma verificação de igualdade de valor usando a reflexão para comparar os valores de cada campo de tipo. Quando um implementador substitui o virtual Equals método em um stuct, o objetivo é fornecer um meio mais eficiente de realizar a verificação de igualdade do valor e, opcionalmente, para basear a comparação de alguns subconjuntos de campo do struct ou propriedades.
O = = e ! = operadores não podem operar em uma struct, a menos que explicitamente struct overloads-los.
Consulte também
Referência
Diretrizes para a implementação de Equals e o operador de igualdade (= =)