A simple class that implements IComparable
, IComparable<T>
, IEquatable<T>
and overrides object.Equals(object)
, object.GetHashCode()
and the various "standard" operators ==
, !=
, >
, <
, >=
, <=
.
Note the use of object.ReferenceEquals(object, object)
so as not to trigger StackOverflowException
. This because we are overloading the ==
and !=
operators and basing them on MyClass.Equals(MyClass)
, so MyClass.Equals(MyClass)
clearly can't use them. A common error is, in fact, inside the
bool Equals(MyClass other)
{
if (other == null)
{
}
}
Booooom! Can't do that. Because the if (other == null)
will recursively call the other.Equals((MyClass)null). What you could do is: if (((object)other) == null)
, because in C# operators can't be virtual
, so here we are using the ==
of the object
class.
InnerEquals
and InnerCompareTo
are present so the null
check mustn't be done twice if the Equals(object)
or CompareTo(object)
are called.
public class MyClass : IComparable<MyClass>, IComparable, IEquatable<MyClass>
{
public int MyInt1 { get; set; }
public int MyInt2 { get; set; }
public int CompareTo(MyClass other)
{
if (object.ReferenceEquals(other, null))
{
return 1;
}
return this.InnerCompareTo(other);
}
int IComparable.CompareTo(object obj)
{
// obj is object, so we can use its == operator
if (obj == null)
{
return 1;
}
MyClass other = obj as MyClass;
if (object.ReferenceEquals(other, null))
{
throw new ArgumentException("obj");
}
return this.InnerCompareTo(other);
}
private int InnerCompareTo(MyClass other)
{
// Here we know that other != null;
if (object.ReferenceEquals(this, other))
{
return 0;
}
int cmp = this.MyInt1.CompareTo(other.MyInt1);
if (cmp == 0)
{
cmp = this.MyInt2.CompareTo(other.MyInt2);
}
return cmp;
}
public override bool Equals(object obj)
{
// obj is object, so we can use its == operator
if (obj == null)
{
return false;
}
MyClass other = obj as MyClass;
if (object.ReferenceEquals(other, null))
{
return false;
}
return this.InnerEquals(other);
}
public bool Equals(MyClass other)
{
if (object.ReferenceEquals(other, null))
{
return false;
}
return this.InnerEquals(other);
}
private bool InnerEquals(MyClass other)
{
// Here we know that other != null;
if (object.ReferenceEquals(this, other))
{
return true;
}
return this.MyInt1 == other.MyInt1 && this.MyInt2 == other.MyInt2;
}
public override int GetHashCode()
{
unchecked
{
// From http://stackoverflow.com/a/263416/613130
int hash = 17;
hash = hash * 23 + this.MyInt1;
hash = hash * 23 + this.MyInt2;
return hash;
}
}
public static bool operator==(MyClass a, MyClass b)
{
if (object.ReferenceEquals(a, null))
{
return object.ReferenceEquals(b, null);
}
return a.Equals(b);
}
// The != is based on the ==
public static bool operator!=(MyClass a, MyClass b)
{
return !(a == b);
}
public static bool operator>(MyClass a, MyClass b)
{
if (object.ReferenceEquals(a, null))
{
return false;
}
return a.CompareTo(b) > 0;
}
// The <, >=, <= are all based on the >
public static bool operator <(MyClass a, MyClass b)
{
return b > a;
}
public static bool operator >=(MyClass a, MyClass b)
{
//return !(a < b);
//We short-circuit the <operator, because we know how it's done
return !(b > a);
}
public static bool operator <=(MyClass a, MyClass b)
{
return !(a > b);
}
}
And this one is the variant for struct
types. A lot shorter, because nearly all the object.ReferenceEquals(object, object)
are gone. Value types can't be null.
public struct MyStruct : IComparable<MyStruct>, IComparable, IEquatable<MyStruct>
{
public int MyInt1 { get; set; }
public int MyInt2 { get; set; }
public int CompareTo(MyStruct other)
{
return this.InnerCompareTo(other);
}
int IComparable.CompareTo(object obj)
{
if (obj == null)
{
return 1;
}
if (!(obj is MyStruct))
{
throw new ArgumentException("obj");
}
MyStruct other = (MyStruct)obj;
return this.InnerCompareTo(other);
}
private int InnerCompareTo(MyStruct other)
{
int cmp = this.MyInt1.CompareTo(other.MyInt1);
if (cmp == 0)
{
cmp = this.MyInt2.CompareTo(other.MyInt2);
}
return cmp;
}
public override bool Equals(object obj)
{
if (obj == null)
{
return false;
}
if (!(obj is MyStruct))
{
throw new ArgumentException("obj");
}
MyStruct other = (MyStruct)obj;
return this.InnerEquals(other);
}
public bool Equals(MyStruct other)
{
return this.InnerEquals(other);
}
private bool InnerEquals(MyStruct other)
{
return this.MyInt1 == other.MyInt1 && this.MyInt2 == other.MyInt2;
}
public override int GetHashCode()
{
unchecked
{
// From http://stackoverflow.com/a/263416/613130
int hash = 17;
hash = hash * 23 + this.MyInt1;
hash = hash * 23 + this.MyInt2;
return hash;
}
}
// The != is based on the ==
public static bool operator ==(MyStruct a, MyStruct b)
{
return a.Equals(b);
}
public static bool operator !=(MyStruct a, MyStruct b)
{
return !(a == b);
}
public static bool operator >(MyStruct a, MyStruct b)
{
return a.CompareTo(b) > 0;
}
// The <, >=, <= are all based on the >
public static bool operator <(MyStruct a, MyStruct b)
{
return b > a;
}
public static bool operator >=(MyStruct a, MyStruct b)
{
//return !(a < b);
//We short-circuit the <operator, because we know how it's done
return !(b > a);
}
public static bool operator <=(MyStruct a, MyStruct b)
{
return !(a > b);
}
}