C# Language
Equals y GetHashCode
Buscar..
Observaciones
Cada implementación de Equals
debe cumplir los siguientes requisitos:
Reflexivo : un objeto debe igualarse a si mismo.
x.Equals(x)
devuelvetrue
.Simétrico : no hay diferencia si comparo x con y o con y con x - el resultado es el mismo.
x.Equals(y)
devuelve el mismo valor quey.Equals(x)
.Transitivo : si un objeto es igual a otro objeto y este es igual a un tercero, el primero debe ser igual al tercero.
si(x.Equals(y) && y.Equals(z))
devuelvetrue
, entoncesx.Equals(z)
devuelvetrue
.Consistente : si compara un objeto con otro varias veces, el resultado es siempre el mismo.
Las invocaciones sucesivas dex.Equals(y)
devuelven el mismo valor siempre que los objetos a los que se hace referencia con x e y no se modifiquen.Comparación a nulo : Ningún objeto es igual a
null
.
x.Equals(null)
devuelvefalse
.
Implementaciones de GetHashCode
:
Compatible con
Equals
: si dos objetos son iguales (lo que significa queEquals
devuelve true),GetHashCode
debe devolver el mismo valor para cada uno de ellos.Rango grande : si dos objetos no son iguales (
Equals
falso), debería haber una alta probabilidad de que sus códigos hash sean distintos. El hashing perfecto a menudo no es posible ya que hay un número limitado de valores para elegir.Barato : debería ser barato calcular el código hash en todos los casos.
Consulte: Pautas para la sobrecarga de Equals () y Operator ==
Por defecto es igual al comportamiento.
Equals
se declara en la propia clase de Object
.
public virtual bool Equals(Object obj);
Por defecto, Equals
tiene el siguiente comportamiento:
Si la instancia es un tipo de referencia, entonces
Equals
devolverá verdadero solo si las referencias son las mismas.Si la instancia es un tipo de valor, entonces
Equals
devolverá verdadero solo si el tipo y el valor son los mismos.string
es un caso especial. Se comporta como un tipo de valor.
namespace ConsoleApplication
{
public class Program
{
public static void Main(string[] args)
{
//areFooClassEqual: False
Foo fooClass1 = new Foo("42");
Foo fooClass2 = new Foo("42");
bool areFooClassEqual = fooClass1.Equals(fooClass2);
Console.WriteLine("fooClass1 and fooClass2 are equal: {0}", areFooClassEqual);
//False
//areFooIntEqual: True
int fooInt1 = 42;
int fooInt2 = 42;
bool areFooIntEqual = fooInt1.Equals(fooInt2);
Console.WriteLine("fooInt1 and fooInt2 are equal: {0}", areFooIntEqual);
//areFooStringEqual: True
string fooString1 = "42";
string fooString2 = "42";
bool areFooStringEqual = fooString1.Equals(fooString2);
Console.WriteLine("fooString1 and fooString2 are equal: {0}", areFooStringEqual);
}
}
public class Foo
{
public string Bar { get; }
public Foo(string bar)
{
Bar = bar;
}
}
}
Escribiendo un buen reemplazo de GetHashCode
GetHashCode
tiene efectos de rendimiento importantes en Dictionary <> y HashTable.
Buenos métodos GetHashCode
- debe tener una distribución uniforme
- cada entero debe tener una probabilidad aproximadamente igual de regresar para una instancia aleatoria
- Si su método devuelve el mismo entero (por ejemplo, la constante '999') para cada instancia, tendrá un mal rendimiento.
- debería ser rápido
- Estos NO son hashes criptográficos, donde la lentitud es una característica
- Cuanto más lenta sea la función hash, más lento será su diccionario.
- debe devolver el mismo HashCode en dos instancias que
Equals
evalúa como verdadero- si no lo hacen (por ejemplo, porque
GetHashCode
devuelve un número aleatorio), es posible que los elementos no se encuentren en unaList
,Dictionary
o similar.
- si no lo hacen (por ejemplo, porque
Un buen método para implementar GetHashCode
es usar un número primo como valor de inicio y agregar los hashcodes de los campos del tipo multiplicado por otros números primos a eso:
public override int GetHashCode()
{
unchecked // Overflow is fine, just wrap
{
int hash = 3049; // Start value (prime number).
// Suitable nullity checks etc, of course :)
hash = hash * 5039 + field1.GetHashCode();
hash = hash * 883 + field2.GetHashCode();
hash = hash * 9719 + field3.GetHashCode();
return hash;
}
}
Solo los campos que se usan en el método Equals
deben usarse para la función hash.
Si necesita tratar el mismo tipo de diferentes maneras para Dictionary / HashTables, puede usar IEqualityComparer.
Sobrescribe Equals y GetHashCode en tipos personalizados
Para una Person
clase como:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public string Clothes { get; set; }
}
var person1 = new Person { Name = "Jon", Age = 20, Clothes = "some clothes" };
var person2 = new Person { Name = "Jon", Age = 20, Clothes = "some other clothes" };
bool result = person1.Equals(person2); //false because it's reference Equals
Pero definiendo Equals
y GetHashCode
como sigue:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public string Clothes { get; set; }
public override bool Equals(object obj)
{
var person = obj as Person;
if(person == null) return false;
return Name == person.Name && Age == person.Age; //the clothes are not important when comparing two persons
}
public override int GetHashCode()
{
return Name.GetHashCode()*Age;
}
}
var person1 = new Person { Name = "Jon", Age = 20, Clothes = "some clothes" };
var person2 = new Person { Name = "Jon", Age = 20, Clothes = "some other clothes" };
bool result = person1.Equals(person2); // result is true
También al usar LINQ para hacer diferentes consultas sobre personas, se verificará Equals
y GetHashCode
:
var persons = new List<Person>
{
new Person{ Name = "Jon", Age = 20, Clothes = "some clothes"},
new Person{ Name = "Dave", Age = 20, Clothes = "some other clothes"},
new Person{ Name = "Jon", Age = 20, Clothes = ""}
};
var distinctPersons = persons.Distinct().ToList();//distinctPersons has Count = 2
Equals y GetHashCode en IEqualityComparator
Para el tipo de Person
dado:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public string Clothes { get; set; }
}
List<Person> persons = new List<Person>
{
new Person{ Name = "Jon", Age = 20, Clothes = "some clothes"},
new Person{ Name = "Dave", Age = 20, Clothes = "some other clothes"},
new Person{ Name = "Jon", Age = 20, Clothes = ""}
};
var distinctPersons = persons.Distinct().ToList();// distinctPersons has Count = 3
Pero definiendo Equals
y GetHashCode
en un IEqualityComparator
:
public class PersonComparator : IEqualityComparer<Person>
{
public bool Equals(Person x, Person y)
{
return x.Name == y.Name && x.Age == y.Age; //the clothes are not important when comparing two persons;
}
public int GetHashCode(Person obj) { return obj.Name.GetHashCode() * obj.Age; }
}
var distinctPersons = persons.Distinct(new PersonComparator()).ToList();// distinctPersons has Count = 2
Tenga en cuenta que para esta consulta, dos objetos se han considerado iguales si tanto el valor Equals
devuelto como el código GetHashCode
han devuelto el mismo código hash para las dos personas.