GetHashCode has major performance effects on Dictionary<> and HashTable.
Good GetHashCode Methods
should have an even distribution
- every integer should have a roughly equal chance of returning for a random instance
- if your method returns the same integer (e.g. the constant '999') for each instance, you'll have bad performance
These are NOT cryptographic hashes, where slowness is a feature
the slower your hash function, the slower your dictionary
if they do not (e.g. because GetHashCode returns a random number), items may not be found in a List, Dictionary, or similar.
A good method to implement GetHashCode is to use one prime number as a starting value, and add the hashcodes of the fields of the type multiplied by other prime numbers to that:
publicoverrideintGetHashCode(){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;}}
Only the fields which are used in the Equals-method should be used for the hash function.
If you have a need to treat the same type in different ways for Dictionary/HashTables, you can use IEqualityComparer.
Default Equals behavior.
Equals is declared in the Object class itself.
publicvirtualboolEquals(Object obj);
By default, Equals has the following behavior:
If the instance is a reference type, then `Equals` will return true only if the references are the same.
If the instance is a value type, then `Equals` will return true only if the type and value are the same.
`string` is a special case. It behaves like a value type.
namespaceConsoleApplication{publicclassProgram{publicstaticvoidMain(string[] args){//areFooClassEqual: FalseFoo fooClass1 =newFoo("42");Foo fooClass2 =newFoo("42");bool areFooClassEqual = fooClass1.Equals(fooClass2);
Console.WriteLine("fooClass1 and fooClass2 are equal: {0}", areFooClassEqual);//False//areFooIntEqual: Trueint fooInt1 =42;int fooInt2 =42;bool areFooIntEqual = fooInt1.Equals(fooInt2);
Console.WriteLine("fooInt1 and fooInt2 are equal: {0}", areFooIntEqual);//areFooStringEqual: Truestring fooString1 ="42";string fooString2 ="42";bool areFooStringEqual = fooString1.Equals(fooString2);
Console.WriteLine("fooString1 and fooString2 are equal: {0}", areFooStringEqual);}}publicclassFoo{publicstring Bar {get;}publicFoo(string bar){
Bar = bar;}}}
Override Equals and GetHashCode on custom types
For a class Person like:
publicclassPerson{publicstring Name {get;set;}publicint Age {get;set;}publicstring Clothes {get;set;}}var person1 =newPerson{ Name ="Jon", Age =20, Clothes ="some clothes"};var person2 =newPerson{ Name ="Jon", Age =20, Clothes ="some other clothes"};bool result = person1.Equals(person2);//false because it's reference Equals
But defining Equals and GetHashCode as follows:
publicclassPerson{publicstring Name {get;set;}publicint Age {get;set;}publicstring Clothes {get;set;}publicoverrideboolEquals(object obj){var person = obj asPerson;if(person ==null)returnfalse;return Name == person.Name && Age == person.Age;//the clothes are not important when comparing two persons}publicoverrideintGetHashCode(){return Name.GetHashCode()*Age;}}var person1 =newPerson{ Name ="Jon", Age =20, Clothes ="some clothes"};var person2 =newPerson{ Name ="Jon", Age =20, Clothes ="some other clothes"};bool result = person1.Equals(person2);// result is true
Also using LINQ to make different queries on persons will check both Equals and GetHashCode:
var persons =newList<Person>{newPerson{ Name ="Jon", Age =20, Clothes ="some clothes"},newPerson{ Name ="Dave", Age =20, Clothes ="some other clothes"},newPerson{ Name ="Jon", Age =20, Clothes =""}};var distinctPersons = persons.Distinct().ToList();//distinctPersons has Count = 2
Equals and GetHashCode in IEqualityComparator
For given type Person:
publicclassPerson{publicstring Name {get;set;}publicint Age {get;set;}publicstring Clothes {get;set;}}List<Person> persons =newList<Person>{newPerson{ Name ="Jon", Age =20, Clothes ="some clothes"},newPerson{ Name ="Dave", Age =20, Clothes ="some other clothes"},newPerson{ Name ="Jon", Age =20, Clothes =""}};var distinctPersons = persons.Distinct().ToList();// distinctPersons has Count = 3
But defining Equals and GetHashCode into an IEqualityComparator :
publicclassPersonComparator:IEqualityComparer<Person>{publicboolEquals(Person x,Person y){return x.Name == y.Name && x.Age == y.Age;//the clothes are not important when comparing two persons;}publicintGetHashCode(Person obj){return obj.Name.GetHashCode()* obj.Age;}}var distinctPersons = persons.Distinct(newPersonComparator()).ToList();// distinctPersons has Count = 2
Note that for this query, two objects have been considered equal if both the Equals returned true and the GetHashCode have returned the same hash code for the two persons.
Remarks
Each implementation of Equals must fulfil the following requirements:
**Reflexive**: An object must equal itself. `x.Equals(x)` returns `true`.
**Symmetric**: There is no difference if I compare x to y or y to x - the result is the same. `x.Equals(y)` returns the same value as `y.Equals(x)`.
**Transitive**: If one object is equal to another object and this one is equal to a third one, the first has to be equal to the third. if `(x.Equals(y) && y.Equals(z))` returns `true`, then `x.Equals(z)` returns `true`.
**Consistent**: If you compare an object to another multiple times, the result is always the same. Successive invocations of `x.Equals(y)` return the same value as long as the objects referenced by x and y are not modified.
**Comparison to null**: No object is equal to `null`. `x.Equals(null)` returns `false`.
Implementations of GetHashCode:
**Compatible with `Equals`**: If two objects are equal (meaning that `Equals` returns true), then `GetHashCode` **must** return the same value for each of them.
**Large range**: If two objects are not equal (`Equals` says false), there should be a **high probability** their hash codes are distinct. **Perfect** hashing is often not possible as there is a limited number of values to choose from.
**Cheap**: It should be inexpensive to calculate the hash code in all cases.