I want to look at some specific scenarios based on the answers above, and my own experiences.
A rule of thumb is, two instances with different hash codes should always be not equal, but if the have the same hash code they might or might not be equals. GetHashCode()
is used to differentiate between instances quickly, and Equals()
is used to verify equality (whatever that means to you).
Also a lot of built-in mechanisms look for an implementation of IEquatable<T>
so it is a good idea to declare an override of Equals(MyClass)
that actually does the checking.
Class with unique ID
Consider a class with a unique ID. Then the equals operation would just check the id. The same with the hash, which solely relies on the id.
public class IdClass : IEquatable<IdClass>
{
public int ID { get; } // Assume unique
public string Name { get; }
#region IEquatable Members
/// <summary>
/// Equality overrides from <see cref="System.Object"/>
/// </summary>
/// <param name="obj">The object to compare this with</param>
/// <returns>False if object is a different type, otherwise it calls <code>Equals(IdClass)</code></returns>
public override bool Equals(object obj)
{
if (obj is IdClass other)
{
return Equals(other);
}
return false;
}
/// <summary>
/// Checks for equality among <see cref="IdClass"/> classes
/// </summary>
/// <param name="other">The other <see cref="IdClass"/> to compare it to</param>
/// <returns>True if equal</returns>
public virtual bool Equals(IdClass other)
{
if (other == null) return false;
return ID.Equals(other.ID);
}
/// <summary>
/// Calculates the hash code for the <see cref="IdClass"/>
/// </summary>
/// <returns>The int hash value</returns>
public override int GetHashCode() => ID.GetHashCode();
#endregion
}
Class with properties
This case is similar to the above, but the comparisons depend on two or more properties, and the need to be combined asymmetrically in the hash code. This will become more evident in the next scenario, but the idea is if one property has hash A
and the other property hash B
, the result should difference from the case where first property has hash B
and the other hash A
.
public class RefClass : IEquatable<RefClass>
{
public string Name { get; }
public int Age { get; }
#region IEquatable Members
/// <summary>
/// Equality overrides from <see cref="System.Object"/>
/// </summary>
/// <param name="obj">The object to compare this with</param>
/// <returns>False if object is a different type, otherwise it calls <code>Equals(RefClass)</code></returns>
public override bool Equals(object obj)
{
if (obj is RefClass other)
{
return Equals(other);
}
return false;
}
/// <summary>
/// Checks for equality among <see cref="RefClass"/> classes
/// </summary>
/// <param name="other">The other <see cref="RefClass"/> to compare it to</param>
/// <returns>True if equal</returns>
public virtual bool Equals(RefClass other)
{
if (other == null) { return false; }
return Name.Equals(other.Name)
&& Age.Equals(other.Age);
}
/// <summary>
/// Calculates the hash code for the <see cref="RefClass"/>
/// </summary>
/// <returns>The int hash value</returns>
public override int GetHashCode()
{
unchecked
{
int hc = -1817952719;
hc = (-1521134295) * hc + Name.GetHashCode();
hc = (-1521134295) * hc + Age.GetHashCode();
return hc;
}
}
#endregion
}
Value based class (structure)
This is almost identical to the case above, except being a value type (struct
declaration) requires also re-definition of ==
and !=
to call equals.
public struct ValClass : IEquatable<ValClass>
{
public int X { get; }
public int Y { get; }
#region IEquatable Members
/// <summary>
/// Equality overrides from <see cref="System.Object"/>
/// </summary>
/// <param name="obj">The object to compare this with</param>
/// <returns>False if object is a different type, otherwise it calls <code>Equals(ValClass)</code></returns>
public override bool Equals(object obj)
{
if (obj is ValClass other)
{
return Equals(other);
}
return false;
}
public static bool operator ==(ValClass target, ValClass other) { return target.Equals(other); }
public static bool operator !=(ValClass target, ValClass other) { return !(target == other); }
/// <summary>
/// Checks for equality among <see cref="ValClass"/> classes
/// </summary>
/// <param name="other">The other <see cref="ValClass"/> to compare it to</param>
/// <returns>True if equal</returns>
public bool Equals(ValClass other)
{
return X == other.X && Y == other.Y;
}
/// <summary>
/// Calculates the hash code for the <see cref="ValClass"/>
/// </summary>
/// <returns>The int hash value</returns>
public override int GetHashCode()
{
unchecked
{
int hc = -1817952719;
hc = (-1521134295) * hc + X.GetHashCode();
hc = (-1521134295) * hc + Y.GetHashCode();
return hc;
}
}
#endregion
}
Note that struct
should be immutable, and it is a good idea to add the readonly
keyword in the declaration
public readonly struct ValClass : IEquatable<ValClass>
{
}
return ("MyClass" + this.Id).GetHashCode();
(just something you might want to keep in mind with GetHashCode) – Perez