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 cheEqualsrestituisce true), alloraGetHashCodedeve restituire lo stesso valore per ognuno di essi.Ampio intervallo : se due oggetti non sono uguali (
Equalsdice 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,
Equalsrestituirà true solo se i riferimenti sono uguali.Se l'istanza è un tipo di valore,
Equalsrestituirà 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
Equalsrestituisce true- se non lo fanno (es. perché
GetHashCoderestituisce un numero casuale), gli elementi potrebbero non essere trovati in unList,Dictionaryo 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.