Many of my questions here on SO concerns IEquatable implementation. I found it being extremely difficult to implement correctly, because there are many hidden bugs in the naïve implementation, and the articles I found about it are quite incomplete. I want to find or write a definitive reference which must include:
Such a complete reference already exists?
PS: Even MSDN reference seems flawed to me
IEquatable<T>
for a Value TypeImplementing IEquatable<T>
for a value type is a little bit different than for a reference type. Let's assume we have the Implement-Your-Own-Value-Type archetype, a Complex number struct.
public struct Complex
{
public double RealPart { get; set; }
public double ImaginaryPart { get; set; }
}
Our first step would be to implement IEquatable<T>
and override Object.Equals
and Object.GetHashCode
:
public bool Equals(Complex other)
{
// Complex is a value type, thus we don't have to check for null
// if (other == null) return false;
return (this.RealPart == other.RealPart)
&& (this.ImaginaryPart == other.ImaginaryPart);
}
public override bool Equals(object other)
{
// other could be a reference type, the is operator will return false if null
if (other is Complex)
return this.Equals((Complex)other);
else
return false;
}
public override int GetHashCode()
{
return this.RealPart.GetHashCode() ^ this.ImaginaryPart.GetHashCode();
}
With very little effort we have a correct implementation, excepting the operators. Adding the operators is also a trivial process:
public static bool operator ==(Complex term1, Complex term2)
{
return term1.Equals(term2);
}
public static bool operator !=(Complex term1, Complex term2)
{
return !term1.Equals(term2);
}
An astute reader would notice that we should probably implement IEquatable<double>
since Complex
numbers could be interchangeable with the underlying value type.
public bool Equals(double otherReal)
{
return (this.RealPart == otherReal) && (this.ImaginaryPart == 0.0);
}
public override bool Equals(object other)
{
// other could be a reference type, thus we check for null
if (other == null) return base.Equals(other);
if (other is Complex)
{
return this.Equals((Complex)other);
}
else if (other is double)
{
return this.Equals((double)other);
}
else
{
return false;
}
}
We need four operators if we add IEquatable<double>
, because you can have Complex == double
or double == Complex
(and the same for operator !=
):
public static bool operator ==(Complex term1, double term2)
{
return term1.Equals(term2);
}
public static bool operator ==(double term1, Complex term2)
{
return term2.Equals(term1);
}
public static bool operator !=(Complex term1, double term2)
{
return !term1.Equals(term2);
}
public static bool operator !=(double term1, Complex term2)
{
return !term2.Equals(term1);
}
So there you have it, with minimal effort we have a correct and useful implementation IEquatable<T>
for a value type:
public struct Complex : IEquatable<Complex>, IEquatable<double>
{
}