C# Language
Uguale e GetHashCode
Ricerca…
Osservazioni
Ogni implementazione di Equals
deve soddisfare i seguenti requisiti:
Riflessivo : un oggetto deve essere uguale a se stesso.
x.Equals(x)
restituiscetrue
.Simmetrico : non vi è alcuna differenza se si confronta x con y o y con x - il risultato è lo stesso.
x.Equals(y)
restituisce lo stesso valore diy.Equals(x)
.Transitivo : se un oggetto è uguale a un altro oggetto e questo è uguale a un terzo, il primo deve essere uguale al terzo.
if(x.Equals(y) && y.Equals(z))
restituiscetrue
, quindix.Equals(z)
restituiscetrue
.Coerentemente : se si confronta un oggetto con un altro più volte, il risultato è sempre lo stesso.
Le successive chiamate dix.Equals(y)
restituiscono lo stesso valore fintanto che gli oggetti referenziati da x e y non vengono modificati.Confronto con null : nessun oggetto è uguale a
null
.
x.Equals(null)
restituiscefalse
.
Implementazioni di GetHashCode
:
Compatibile con
Equals
: se due oggetti sono uguali (ovvero cheEquals
restituisce true), alloraGetHashCode
deve restituire lo stesso valore per ognuno di essi.Ampio intervallo : se due oggetti non sono uguali (
Equals
dice false), ci dovrebbe essere un'alta probabilità che i loro codici hash siano distinti. Spesso l'hashing perfetto non è possibile in quanto esiste un numero limitato di valori tra cui scegliere.Economico : dovrebbe essere poco costoso calcolare il codice hash in tutti i casi.
Vedi: Linee guida per il sovraccarico di Equals () e Operatore ==
Predefinito Comportamento uguale.
Equals
è dichiarato nella classe Object
stessa.
public virtual bool Equals(Object obj);
Per impostazione predefinita, Equals
ha il seguente comportamento:
Se l'istanza è un tipo di riferimento,
Equals
restituirà true solo se i riferimenti sono uguali.Se l'istanza è un tipo di valore,
Equals
restituirà true solo se il tipo e il valore sono uguali.string
è un caso speciale. Si comporta come un tipo di valore.
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;
}
}
}
Scrittura di un buon override GetHashCode
GetHashCode
ha importanti effetti sulle prestazioni del dizionario <> e HashTable.
Buoni metodi GetHashCode
- dovrebbe avere una distribuzione uniforme
- ogni numero intero dovrebbe avere una probabilità all'incirca uguale di restituire per un'istanza casuale
- se il tuo metodo restituisce lo stesso numero intero (ad es. la costante '999') per ogni istanza, avrai cattive prestazioni
- dovrebbe essere veloce
- Questi NON sono hash crittografici, dove la lentezza è una caratteristica
- più lenta è la tua funzione hash, più lento è il tuo dizionario
- deve restituire lo stesso HashCode su due istanze che
Equals
restituisce true- se non lo fanno (es. perché
GetHashCode
restituisce un numero casuale), gli elementi potrebbero non essere trovati in unList
,Dictionary
o simile.
- se non lo fanno (es. perché
Un buon metodo per implementare GetHashCode
consiste nell'utilizzare un numero primo come valore iniziale e aggiungere gli hashcode dei campi del tipo moltiplicato per altri numeri primi a quello:
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 i campi che sono usati in Equals
-method dovrebbero essere usati per la funzione hash.
Se si ha la necessità di trattare lo stesso tipo in modi diversi per Dizionario / HashTables, è possibile utilizzare IEqualityComparer.
Override Equals e GetHashCode su tipi personalizzati
Per una classe Person
come:
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
Ma definendo Equals
e GetHashCode
come segue:
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
Anche usando LINQ per fare query diverse su persone controllerà sia Equals
che 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
Uguale a GetHashCode in IEqualityComparator
Per tipo di dato Person
:
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
Ma definendo Equals
e GetHashCode
in 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
Si noti che per questa query, due oggetti sono stati considerati uguali se entrambi gli Equals
restituito true e il GetHashCode
ha restituito lo stesso codice hash per le due persone.