C# Language
Est égal à et GetHashCode
Recherche…
Remarques
Chaque implémentation de Equals
doit répondre aux exigences suivantes:
Réfléchissant : Un objet doit être égal à lui-même.
x.Equals(x)
renvoietrue
.Symétrique : Il n'y a pas de différence si je compare x à y ou y à x - le résultat est le même.
x.Equals(y)
renvoie la même valeur quey.Equals(x)
.Transitif : Si un objet est égal à un autre objet et que celui-ci est égal à un troisième, le premier doit être égal au troisième.
if(x.Equals(y) && y.Equals(z))
renvoietrue
, alorsx.Equals(z)
renvoietrue
.Cohérent : Si vous comparez un objet à plusieurs fois, le résultat est toujours le même.
Les invocations successives dex.Equals(y)
renvoient la même valeur tant que les objets référencés par x et y ne sont pas modifiés.Comparaison à null : Aucun objet n'est égal à
null
.
x.Equals(null)
renvoiefalse
.
Implémentations de GetHashCode
:
Compatible avec
Equals
: Si deux objets sont égaux (ce qui signifie queEquals
renvoie true),GetHashCode
doit renvoyer la même valeur pour chacun d'eux.Large range : Si deux objets ne sont pas égaux (
Equals
dit false), il devrait y avoir une forte probabilité que leurs codes de hachage soient distincts. Un hachage parfait n'est souvent pas possible car il y a un nombre limité de valeurs à choisir.Bon marché : le calcul du code de hachage dans tous les cas devrait être peu coûteux.
Voir: Directives pour la surcharge d'Equals () et de l'opérateur ==
Comportement par défaut égal.
Equals
est déclaré dans la classe Object
elle-même.
public virtual bool Equals(Object obj);
Par défaut, Equals
a le comportement suivant:
Si l'instance est un type de référence, alors
Equals
renvoie true uniquement si les références sont identiques.Si l'instance est un type de valeur, alors
Equals
renvoie true uniquement si le type et la valeur sont identiques.string
est un cas particulier. Il se comporte comme un type de valeur.
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;
}
}
}
Écrire un bon remplacement GetHashCode
GetHashCode
a des effets de performance majeurs sur Dictionary <> et HashTable.
Bonnes méthodes GetHashCode
- devrait avoir une distribution uniforme
- chaque entier devrait avoir une chance à peu près égale de retourner pour une instance aléatoire
- si votre méthode retourne le même entier (par exemple, la constante '999') pour chaque instance, vous aurez de mauvaises performances
- devrait être rapide
- Ce ne sont PAS des hachages cryptographiques, où la lenteur est une caractéristique
- plus votre fonction de hachage est lente, plus votre dictionnaire est lent
- doit renvoyer le même HashCode sur deux instances évaluées par
Equals
à true- S'ils ne le font pas (par exemple parce que
GetHashCode
renvoie un nombre aléatoire), les éléments ne peuvent pas être trouvés dans uneList
, unDictionary
ou un élément similaire.
- S'ils ne le font pas (par exemple parce que
Une bonne méthode pour implémenter GetHashCode
consiste à utiliser un nombre premier comme valeur de départ et à ajouter les codes de hachage des champs du type multiplié par d'autres nombres premiers:
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;
}
}
Seuls les champs utilisés dans la méthode Equals
doivent être utilisés pour la fonction de hachage.
Si vous avez besoin de traiter le même type de différentes manières pour Dictionary / HashTables, vous pouvez utiliser IEqualityComparer.
Remplacer Equals et GetHashCode sur les types personnalisés
Pour une classe Person
comme:
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
Mais définir Equals
et GetHashCode
comme suit:
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
L'utilisation de LINQ pour effectuer des requêtes différentes sur des personnes vérifie également Equals
et 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
Est égal à et GetHashCode dans IEqualityComparator
Pour le type donné 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
Mais définir Equals
et GetHashCode
dans 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
Notez que pour cette requête, deux objets ont été considérés égaux si les deux Equals
renvoyés sont true et que GetHashCode
a renvoyé le même code de hachage pour les deux personnes.