C# Language
Lika och GetHashCode
Sök…
Anmärkningar
Varje implementering av Equals
måste uppfylla följande krav:
Reflexivt : Ett objekt måste lika sig.
x.Equals(x)
returnerartrue
.Symmetrisk : Det finns ingen skillnad om jag jämför x till y eller y till x - resultatet är detsamma.
x.Equals(y)
returnerar samma värde somy.Equals(x)
.Transitiv : Om ett objekt är lika med ett annat objekt och det här är lika med ett tredje måste det första vara lika med det tredje.
om(x.Equals(y) && y.Equals(z))
returnerartrue
,x.Equals(z)
true
.Konsekvent : Om du jämför ett objekt med en annan flera gånger är resultatet alltid detsamma.
På varandra följande invokationer avx.Equals(y)
samma värde så länge de objekt som refereras av x och y inte ändras.Jämförelse med noll : Inget objekt är lika med
null
.
x.Equals(null)
returnerarfalse
.
Implementeringar av GetHashCode
:
Kompatibel med
Equals
: Om två objekt är lika (vilket innebär attEquals
returnerar true), sedanGetHashCode
måste returnera samma värde för var och en av dem.Stort intervall : Om två objekt inte är lika (
Equals
säger falskt) bör det vara stor sannolikhet att deras hashkoder är distinkta. Perfekt hashing är ofta inte möjligt eftersom det finns ett begränsat antal värden att välja mellan.Billigt : Det bör vara billigt att beräkna hashkoden i alla fall.
Se: Riktlinjer för överbelastning av lika () och operatör ==
Standard Lika beteende.
Equals
är deklarerade i själva Object
.
public virtual bool Equals(Object obj);
Som standard har Equals
följande beteende:
Om instansen är en referenstyp, kommer
Equals
att gälla om referenserna är desamma.Om instansen är en värdetyp, kommer
Equals
att gälla om typen och värdet är desamma.string
är ett speciellt fall. Det uppför sig som en värdetyp.
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;
}
}
}
Att skriva en bra åsidosättande av GetHashCode
GetHashCode
har stora resultateffekter på Dictionary <> och HashTable.
Bra GetHashCode
metoder
- bör ha en jämn fördelning
- varje heltal bör ha en ungefär lika stor chans att återvända för en slumpmässig instans
- Om din metod returnerar samma heltal (t.ex. konstanten '999') för varje instans har du dålig prestanda
- borde vara snabb
- Dessa är INTE kryptografiska hash, där långsamhet är en funktion
- ju långsammare din hash-funktion, desto långsammare din ordbok
- måste returnera samma HashCode i två fall som
Equals
utvärderar till true- om de inte gör det (t.ex. för att
GetHashCode
returnerar ett slumpmässigt antal), kan det hända att objekt inte finns i enList
,Dictionary
eller liknande.
- om de inte gör det (t.ex. för att
En bra metod för att implementera GetHashCode
är att använda ett primtal som ett startvärde och lägga till hashkoderna för fälten av typen multiplicerat med andra primtal till det:
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;
}
}
Endast fälten som används i Equals
metoden ska användas för hash-funktionen.
Om du har behov av att behandla samma typ på olika sätt för Dictionary / HashTables, kan du använda IEqualityComparer.
Överskridande Equals och GetHashCode på anpassade typer
För en Person
som:
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
Men definiera Equals
och GetHashCode
enligt följande:
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
Om du använder LINQ för att göra olika frågor på personer kommer du att kontrollera både Equals
och 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
Lika och GetHashCode i IEqualityComparator
För given 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
Men att definiera Equals
och GetHashCode
till en 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
Observera att för denna fråga har två objekt betraktats som lika om både Equals
returnerade sanna och GetHashCode
har returnerat samma hash-kod för de två personerna.