For a class, the defaults are essentially reference equality, and that is usually fine. If writing a struct, it is more common to override equality (not least to avoid boxing), but it is very rare you write a struct anyway!
When overriding equality, you should always have a matching Equals() and GetHashCode() (i.e. for two values, if Equals() returns true they must return the same hash-code, but the converse is not required) – and it is common to also provide ==/!= operators, and often to implement IEquatable<T> too.
These days, when generating a hash, the HashCode utility type is very useful; for example:
return HashCode.Combine(field1, field2); // multiple overloads available here
When that isn’t available:
For generating the hash code, it is common to use a factored sum, as this avoids collisions on paired values – for example, for a basic 2 field hash:
unchecked // disable overflow, for the unlikely possibility that you
{ // are compiling with overflow-checking enabled
int hash = 27;
hash = (13 * hash) + field1.GetHashCode();
hash = (13 * hash) + field2.GetHashCode();
return hash;
}
This has the advantage that:
- the hash of {1,2} is not the same as the hash of {2,1}
- the hash of {1,1} is not the same as the hash of {2,2}
etc – which can be common if just using an unweighted sum, or xor (^), etc.