C# Language
Równa się i GetHashCode
Szukaj…
Uwagi
Każde wdrożenie Equals
musi spełniać następujące wymagania:
Odruchowe : Obiekt musi się równać.
x.Equals(x)
zwraca wartośćtrue
.Symetryczny : nie ma różnicy, jeśli porównam x do y lub y do x - wynik jest taki sam.
x.Equals(y)
zwraca tę samą wartość coy.Equals(x)
.Przechodnie : Jeśli jeden obiekt jest równy drugiemu, a ten jest równy trzeciemu, pierwszy musi być równy trzeciemu.
jeśli(x.Equals(y) && y.Equals(z))
zwracatrue
, tox.Equals(z)
zwracatrue
.Spójny : Jeśli porównasz obiekt z innym wiele razy, wynik będzie zawsze taki sam.
Kolejne wywołaniax.Equals(y)
zwracają tę samą wartość, o ile obiekty, do których odnoszą się xiy, nie są modyfikowane.Porównanie do null : Żaden obiekt nie jest równy
null
.
x.Equals(null)
zwracafalse
.
Implementacje GetHashCode
:
Kompatybilny z
Equals
: Jeśli dwa obiekty są równe (co oznacza, żeEquals
zwraca true), wówczasGetHashCode
musi zwrócić tę samą wartość dla każdego z nich.Duży zakres: Jeśli dwa obiekty nie są równe (
Equals
mówi false), nie powinno być wysokie prawdopodobieństwo ich kody hash są różne. Idealne haszowanie często nie jest możliwe, ponieważ istnieje ograniczona liczba wartości do wyboru.Tani : We wszystkich przypadkach obliczenie kodu skrótu powinno być tanie.
Patrz: Wytyczne dotyczące przeciążania równa się () i operator ==
Domyślne zachowanie równe.
Equals
jest zadeklarowana w samej klasie Object
.
public virtual bool Equals(Object obj);
Domyślnie Equals
ma następujące zachowanie:
Jeśli instancja jest typem odwołania, wówczas
Equals
zwróci wartość true tylko wtedy, gdy odwołania są takie same.Jeśli instancja jest typem wartości, wówczas
Equals
zwróci wartość true tylko wtedy, gdy typ i wartość są takie same.string
jest specjalnym przypadkiem. Zachowuje się jak typ wartości.
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;
}
}
}
Pisanie dobrego zastąpienia GetHashCode
GetHashCode
ma znaczący wpływ na wydajność na Dictionary <> i HashTable.
Dobre metody GetHashCode
- powinien mieć równomierny rozkład
- każda liczba całkowita powinna mieć mniej więcej taką samą szansę na powrót w przypadkowej instancji
- jeśli twoja metoda zwraca tę samą liczbę całkowitą (np. stałą „999”) dla każdej instancji, będziesz mieć słabą wydajność
- powinno być szybkie
- NIE są to hashery kryptograficzne, w których spowolnienie jest cechą
- im wolniejsza jest funkcja skrótu, tym wolniej działa słownik
- musi zwrócić ten sam kod HashCode w dwóch instancjach, które
Equals
ocenia na true- jeśli nie (np. ponieważ
GetHashCode
zwraca liczbę losową), elementów nie można znaleźć naList
,Dictionary
itp.
- jeśli nie (np. ponieważ
Dobrą metodą zaimplementowania GetHashCode
jest użycie jednej liczby pierwszej jako wartości początkowej i dodanie do niej skrótów pól typu pomnożonych przez inne liczby pierwsze:
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;
}
}
Do funkcji skrótu należy używać tylko pól używanych w metodzie Equals
.
Jeśli musisz traktować ten sam typ na różne sposoby dla Dictionary / HashTables, możesz użyć IEqualityComparer.
Zastąp równe i GetHashCode na niestandardowych typach
Dla klasy Person
taka jak:
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
Ale zdefiniowanie Equals
i GetHashCode
w następujący sposób:
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
Również użycie LINQ do różnych zapytań dotyczących osób sprawdzi zarówno Equals
jak i 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
Równa się i GetHashCode w IEqualityComparator
Dla danego typu 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
Ale zdefiniowanie Equals
i GetHashCode
w 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
Zauważ, że dla tego zapytania dwa obiekty zostały uznane za równe, jeśli zarówno Equals
zwróciło wartość true, a GetHashCode
zwrócił ten sam kod skrótu dla dwóch osób.