c# - trabajo - ¿Hay una referencia completa de implementación de IEquatable?
normas apa 2018 (5)
Muchas de mis preguntas aquí sobre SO se refieren a la implementación de IEquatable. Me resultó extremadamente difícil implementarlo correctamente, porque hay muchos errores ocultos en la implementación ingenua, y los artículos que encontré al respecto son bastante incompletos. Quiero encontrar o escribir una referencia definitiva que debe incluir:
- Cómo implementar IEquatable correctamente
- Cómo anular es igual a correctamente
- Cómo anular GetHashCode correctamente
- Cómo implementar el método ToString correctamente.
- Cómo implementar el operador == correctamente
- Cómo implementar el operador! = Correctamente
¿Ya existe una referencia tan completa?
PS: Incluso la referencia de MSDN me parece defectuosa
Implementando IEquatable<T>
para un tipo de valor
La implementación de IEquatable<T>
para un tipo de valor es un poco diferente que para un tipo de referencia. Supongamos que tenemos el arquetipo Implementar su propio valor-tipo, una estructura de número compleja.
public struct Complex
{
public double RealPart { get; set; }
public double ImaginaryPart { get; set; }
}
Nuestro primer paso sería implementar IEquatable<T>
y anular Object.Equals
y Object.GetHashCode
:
public bool Equals(Complex other)
{
// Complex is a value type, thus we don''t have to check for null
// if (other == null) return false;
return (this.RealPart == other.RealPart)
&& (this.ImaginaryPart == other.ImaginaryPart);
}
public override bool Equals(object other)
{
// other could be a reference type, the is operator will return false if null
if (other is Complex)
return this.Equals((Complex)other);
else
return false;
}
public override int GetHashCode()
{
return this.RealPart.GetHashCode() ^ this.ImaginaryPart.GetHashCode();
}
Con muy poco esfuerzo tenemos una implementación correcta, exceptuando a los operadores. Agregar los operadores es también un proceso trivial:
public static bool operator ==(Complex term1, Complex term2)
{
return term1.Equals(term2);
}
public static bool operator !=(Complex term1, Complex term2)
{
return !term1.Equals(term2);
}
Un lector astuto notaría que probablemente deberíamos implementar IEquatable<double>
ya que los números Complex
podrían ser intercambiables con el tipo de valor subyacente.
public bool Equals(double otherReal)
{
return (this.RealPart == otherReal) && (this.ImaginaryPart == 0.0);
}
public override bool Equals(object other)
{
// other could be a reference type, thus we check for null
if (other == null) return base.Equals(other);
if (other is Complex)
{
return this.Equals((Complex)other);
}
else if (other is double)
{
return this.Equals((double)other);
}
else
{
return false;
}
}
Necesitamos cuatro operadores si agregamos IEquatable<double>
, porque puede tener Complex == double
o double == Complex
(¡y lo mismo para operator !=
):
public static bool operator ==(Complex term1, double term2)
{
return term1.Equals(term2);
}
public static bool operator ==(double term1, Complex term2)
{
return term2.Equals(term1);
}
public static bool operator !=(Complex term1, double term2)
{
return !term1.Equals(term2);
}
public static bool operator !=(double term1, Complex term2)
{
return !term2.Equals(term1);
}
Entonces, ahí lo tienen, con un esfuerzo mínimo, tenemos una implementación correcta y útil de IEquatable<T>
para un tipo de valor:
public struct Complex : IEquatable<Complex>, IEquatable<double>
{
}
Al leer MSDN, estoy bastante seguro de que el mejor ejemplo de una implementación adecuada se encuentra en la página del Método IEquatable.Equals . Mi única desviación es la siguiente:
public override bool Equals(Object obj)
{
if (obj == null) return base.Equals(obj);
if (! (obj is Person))
return false; // Instead of throw new InvalidOperationException
else
return Equals(obj as Person);
}
Para aquellos que se preguntan acerca de la desviación, se deriva de la página MSDN de Object.Equals(Object) :
Las implementaciones de iguales no deben lanzar excepciones.
Creo que conseguir algo tan simple como verificar si los objetos son correctos para la igualdad es un poco complicado con el diseño de .NET.
Para Struct
1) Implementar IEquatable<T>
. Mejora notablemente el rendimiento.
2) Ya que tiene sus propios Equals
ahora, anule GetHashCode
, y para ser consistente con varios object.Equals
anulación de verificación de igualdad.
3) Los operadores de sobrecarga ==
y !=
No tienen que hacerse religiosamente, ya que el compilador avisará si equipara involuntariamente una estructura con otra con a ==
o !=
, Pero es bueno hacerlo para que sea coherente con los métodos Equals
.
public struct Entity : IEquatable<Entity>
{
public bool Equals(Entity other)
{
throw new NotImplementedException("Your equality check here...");
}
public override bool Equals(object obj)
{
if (obj == null || !(obj is Entity))
return false;
return Equals((Entity)obj);
}
public static bool operator ==(Entity e1, Entity e2)
{
return e1.Equals(e2);
}
public static bool operator !=(Entity e1, Entity e2)
{
return !(e1 == e2);
}
public override int GetHashCode()
{
throw new NotImplementedException("Your lightweight hashing algorithm, consistent with Equals method, here...");
}
}
Para clase
Desde MS:
La mayoría de los tipos de referencia no deben sobrecargar el operador de igualdad, incluso si reemplazan a Igual a.
Para mí ==
siente como igualdad de valores, más como un azúcar sintáctico para el método Equals
. Escribir a == b
es mucho más intuitivo que escribir a.Equals(b)
. Rara vez tendremos que comprobar la igualdad de referencia. En los niveles abstractos que tratan con representaciones lógicas de objetos físicos, esto no es algo que debamos verificar. Creo que tener diferentes semánticas para ==
y Equals
puede ser confuso. Creo que debería haber sido ==
para igualdad de valor e Igualdad para la igualdad de referencia (o un nombre mejor como IsSameAs
) en primer lugar. Me encantaría no tomarme en serio la guía de la EM, no solo porque no es natural para mí, sino también porque la sobrecarga ==
no hace ningún daño importante. Eso es a diferencia de no anular Equals
o GetHashCode
no genéricos que pueden morder, porque el framework no usa ==
ninguna parte, pero solo si lo usamos nosotros mismos. El único beneficio real que obtengo al no sobrecargar ==
y !=
Será la consistencia con el diseño del marco completo sobre el que no tengo control. Y eso es realmente una gran cosa, tan tristemente me atendré .
Con semántica de referencia (objetos mutables).
1) Override Equals
and GetHashCode
.
2) La implementación de IEquatable<T>
no es una obligación, pero será agradable si tiene una.
public class Entity : IEquatable<Entity>
{
public bool Equals(Entity other)
{
if (ReferenceEquals(this, other))
return true;
if (ReferenceEquals(null, other))
return false;
//if your below implementation will involve objects of derived classes, then do a
//GetType == other.GetType comparison
throw new NotImplementedException("Your equality check here...");
}
public override bool Equals(object obj)
{
return Equals(obj as Entity);
}
public override int GetHashCode()
{
throw new NotImplementedException("Your lightweight hashing algorithm, consistent with Equals method, here...");
}
}
Con valor semántico (objetos inmutables).
Esta es la parte difícil. Puede ser fácilmente desordenado si no se cuida ..
1) Override Equals
and GetHashCode
.
2) Sobrecarga ==
y !=
Para igualar Equals
. Asegúrese de que funciona para nulos .
2) La implementación de IEquatable<T>
no es una obligación, pero será agradable si tiene una.
public class Entity : IEquatable<Entity>
{
public bool Equals(Entity other)
{
if (ReferenceEquals(this, other))
return true;
if (ReferenceEquals(null, other))
return false;
//if your below implementation will involve objects of derived classes, then do a
//GetType == other.GetType comparison
throw new NotImplementedException("Your equality check here...");
}
public override bool Equals(object obj)
{
return Equals(obj as Entity);
}
public static bool operator ==(Entity e1, Entity e2)
{
if (ReferenceEquals(e1, null))
return ReferenceEquals(e2, null);
return e1.Equals(e2);
}
public static bool operator !=(Entity e1, Entity e2)
{
return !(e1 == e2);
}
public override int GetHashCode()
{
throw new NotImplementedException("Your lightweight hashing algorithm, consistent with Equals method, here...");
}
}
Tenga especial cuidado de ver cómo debería funcionar si su clase puede ser heredada, en tales casos tendrá que determinar si un objeto de clase base puede ser igual a un objeto de clase derivado. Idealmente, si no se utiliza ningún objeto de clase derivada para verificar la igualdad, entonces una instancia de clase base puede ser igual a una instancia de clase derivada y, en tales casos, no es necesario verificar la Igualdad de Type
en Equals
genéricos de la clase base.
En general tenga cuidado de no duplicar el código. Podría haber hecho una clase base abstracta genérica ( IEqualizable<T>
o algo así) como una plantilla para permitir la reutilización más fácil, pero, lamentablemente, en C # me impide derivar de clases adicionales.
Encontré otra referencia, es la implementación de .NET Anonymous Type. Para un tipo anónimo con un int y un doble como propiedades desmonté el siguiente código C #:
public class f__AnonymousType0
{
// Fields
public int A { get; }
public double B { get; }
// Methods
public override bool Equals(object value)
{
var type = value as f__AnonymousType0;
return (((type != null)
&& EqualityComparer<int>.Default.Equals(this.A, type.A))
&& EqualityComparer<double>.Default.Equals(this.B, type.B));
}
public override int GetHashCode()
{
int num = -1134271262;
num = (-1521134295 * num) + EqualityComparer<int>.Default.GetHashCode(this.A);
return ((-1521134295 * num) + EqualityComparer<double>.Default.GetHashCode(this.B);
}
public override string ToString()
{
StringBuilder builder = new StringBuilder();
builder.Append("{ A = ");
builder.Append(this.A);
builder.Append(", B = ");
builder.Append(this.B);
builder.Append(" }");
return builder.ToString();
}
}
Solo tengo que derivar de esta clase.
public abstract class DataClass : IEquatable<DataClass>
{
public override bool Equals(object obj)
{
var other = obj as DataClass;
return this.Equals(other);
}
public bool Equals(DataClass other)
{
return (!ReferenceEquals(null, other))
&& this.Execute((self2, other2) =>
other2.Execute((other3, self3) => self3.Equals(other3), self2)
, other);
}
public override int GetHashCode()
{
return this.Execute(obj => obj.GetHashCode());
}
public override string ToString()
{
return this.Execute(obj => obj.ToString());
}
private TOutput Execute<TOutput>(Func<object, TOutput> function)
{
return this.Execute((obj, other) => function(obj), new object());
}
protected abstract TOutput Execute<TParameter, TOutput>(
Func<object, TParameter, TOutput> function,
TParameter other);
}
Y luego implementar el método abstracto como este.
public class Complex : DataClass
{
public double Real { get; set; }
public double Imaginary { get; set; }
protected override TOutput Execute<TParameter, TOutput>(
Func<object, TParameter, TOutput> function,
TParameter other)
{
return function(new
{
Real = this.Real,
Imaginary = this.Imaginary,
}, other);
}
}