C# Language
Equals und GetHashCode
Suche…
Bemerkungen
Jede Implementierung von Equals
muss die folgenden Anforderungen erfüllen:
Reflexiv : Ein Objekt muss sich selbst gleich sein.
x.Equals(x)
gibttrue
.Symmetrisch : Es gibt keinen Unterschied, wenn ich x mit y oder y mit x vergleiche - das Ergebnis ist das gleiche.
x.Equals(y)
liefert den gleichen Wert wiey.Equals(x)
.Transitiv : Wenn ein Objekt einem anderen Objekt entspricht und dieses Objekt einem dritten Objekt entspricht, muss das erste Objekt dem dritten Objekt entsprechen.
wenn(x.Equals(y) && y.Equals(z))
true
zurückgibt, gibtx.Equals(z)
true
.Konsistent : Wenn Sie ein Objekt mehrmals mit einem anderen Objekt vergleichen, ist das Ergebnis immer dasselbe.
Aufeinanderfolgendex.Equals(y)
vonx.Equals(y)
denselben Wert, solange die von x und y referenzierten Objekte nicht geändert werden.Vergleich mit null : Kein Objekt ist gleich
null
.
x.Equals(null)
gibtfalse
.
Implementierungen von GetHashCode
:
Kompatibel mit
Equals
: Wenn zwei Objekte gleich sind (dhEquals
gibt true zurück), mussGetHashCode
für jedesGetHashCode
denselben Wert zurückgeben.Große Reichweite : Wenn zwei Objekte nicht gleich sind (
Equals
sagt falsch), sollten die Hash-Codes mit hoher Wahrscheinlichkeit unterschiedlich sein. Perfektes Hashing ist oft nicht möglich, da eine begrenzte Anzahl von Werten zur Auswahl steht.Günstig : Die Berechnung des Hash-Codes sollte in allen Fällen kostengünstig sein.
Siehe: Richtlinien zum Überladen von Equals () und Operator ==
Standard Gleiches Verhalten.
Equals
wird in der Object
Klasse selbst deklariert.
public virtual bool Equals(Object obj);
Standardmäßig hat Equals
das folgende Verhalten:
Wenn es sich bei der Instanz um einen Referenztyp handelt, gibt
Equals
nur dann true zurück, wenn die Referenzen identisch sind.Wenn es sich bei der Instanz um einen Werttyp handelt, gibt
Equals
nur dann true zurück, wenn Typ und Wert gleich sind.string
ist ein Sonderfall. Es verhält sich wie ein Werttyp.
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;
}
}
}
Eine gute GetHashCode-Überschreibung schreiben
GetHashCode
hat erhebliche Auswirkungen auf die Leistung von Dictionary <> und HashTable.
Gute GetHashCode
Methoden
- sollte eine gleichmäßige Verteilung haben
- Jede ganze Zahl sollte eine ungefähr gleiche Chance haben, für eine zufällige Instanz zurückzukehren
- Wenn Ihre Methode für jede Instanz dieselbe Ganzzahl zurückgibt (z. B. die Konstante '999'), haben Sie eine schlechte Leistung
- sollte schnell sein
- Hierbei handelt es sich NICHT um kryptographische Hashes, bei denen Langsamkeit eine Funktion ist
- Je langsamer Ihre Hash-Funktion ist, desto langsamer ist Ihr Wörterbuch
- muss denselben HashCode in zwei Instanzen zurückgeben, die
Equals
als true auswertet- Wenn dies nicht der
GetHashCode
(z. B. weilGetHashCode
eine Zufallszahl zurückgibt), werden Elemente möglicherweise nicht in einerList
, einemDictionary
oder ähnlichem gefunden.
- Wenn dies nicht der
Eine gute Methode zum Implementieren von GetHashCode
ist die Verwendung einer Primzahl als Startwert und das Hinzufügen der Hashcodes der Felder des Typs multipliziert mit anderen Primzahlen.
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;
}
}
Nur die Felder, die in der Equals
Methode verwendet werden, sollten für die Hash-Funktion verwendet werden.
Wenn Sie denselben Typ für Dictionary / HashTables auf unterschiedliche Weise behandeln müssen, können Sie IEqualityComparer verwenden.
Überschreiben Sie Equals und GetHashCode für benutzerdefinierte Typen
Für eine Klasse Person
wie:
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
Definieren Sie jedoch Equals
und GetHashCode
wie folgt:
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
GetHashCode
LINQ verwenden, um verschiedene Abfragen zu Personen GetHashCode
werden sowohl Equals
als auch 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 und GetHashCode in IEqualityComparator
Für den angegebenen Typ 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
Definieren von Equals
und GetHashCode
in einem 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
Beachten Sie, dass für diese Abfrage zwei Objekte als gleich betrachtet wurden, wenn sowohl Equals
als auch GetHashCode
denselben Hashcode für die beiden Personen zurückgegeben haben.