#421 – Value Equality and IComparable Example

Here’s a full example of a reference type that supports value equality semantics and implements both IEquatable and IComparable.

    public class Rectangle : IEquatable<Rectangle>, IComparable<Rectangle>
    {
        public int Height { get; set; }
        public int Width { get; set; }

        public Rectangle(int height, int width)
        {
            Height = height;
            Width = width;
        }

        public override bool Equals(object obj)
        {
            return this.Equals(obj as Rectangle);
        }

        public override int GetHashCode()
        {
            return Height.GetHashCode() ^ Width.GetHashCode();
        }

        public bool Equals(Rectangle r)
        {
            if (ReferenceEquals(r,null))
                return false;

            return ((Height == r.Height) && (Width == r.Width) ||
                    (Height == r.Width) && (Width == r.Height));
        }

        public static bool operator ==(Rectangle r1, Rectangle r2)
        {
            if (ReferenceEquals(r1, null))
            {
                return ReferenceEquals(r2, null) ? true : false;
            }

            return r1.Equals(r2);
        }

        public static bool operator !=(Rectangle r1, Rectangle r2)
        {
            return !(r1 == r2);
        }

        // Result:
        //  < 0 : this instance less than r
        //  = 0 : this instance equivalent to r
        //  > 0 : this instance greater than r
        public int CompareTo(Rectangle r)
        {
            if (ReferenceEquals(r, null))
                return 1;

            if (this.Equals(r))
                return 0;

            else if (this.Area() == r.Area())
                return this.Width - r.Width;

            else
                return this.Area() - r.Area();
        }

        public static bool operator <(Rectangle r1, Rectangle r2)
        {
            if (ReferenceEquals(r1, null))
                return false;

            else
                return (r1.CompareTo(r2) < 0) ? true : false;
        }

        public static bool operator >(Rectangle r1, Rectangle r2)
        {
            if (ReferenceEquals(r1, null))
                return false;

            else
                return (r1.CompareTo(r2) > 0) ? true : false;
        }

        public static bool operator <=(Rectangle r1, Rectangle r2)
        {
            return (r1 < r2) || (r1 == r2);
        }

        public static bool operator >=(Rectangle r1, Rectangle r2)
        {
            return (r1 > r2) || (r1 == r2);
        }

        public int Area()
        {
            return Height * Width;
        }
    }

#420 – Laundry List for Implementing Value Equality and IComparable

To implement value equality (equivalence) in a reference type, you should do the following:

  • Override Object.Equals
    • In Object.Equals, call the type-specific Equals method using the as operator
  • Implement IEquatable<T> by adding a type-specific Equals method
    • Check for null using ReferenceEquals method
    • Call base class’ Equals method to compare fields that exist in the base class if it also checks for value equality
    • Check for equivalence by comparing individual fields
  • Override GetHashCode, generating a hash code based on fields used for equivalence
  • Overload == operator
    • Check for 1st parameter being null, compare to 2nd parameter
    • Call 1st parameter’s type-specific Equals method
  • Overload != operator, invoking the == operator
  • Implement IComparable<T>, adding a CompareTo method
    • Check for equivalence using Equals method, then compare individual fields
  • Overload < and > operators
    • Check for 1st parameter being null
    • Call 1st parameter’s CompareTo method
  • Overload <= and >= operators
    • Calculate a result using the <, > and == operators

#419 – Override Relational Operators When You Implement IComparable

When a class implements IComparable, it must implement the CompareTo method.  For completeness, you should also override the relational operators.

Here’s an example.

    public class Rectangle : IEquatable<Rectangle>, IComparable<Rectangle>
    {
        public int Height { get; set; }
        public int Width { get; set; }

        public Rectangle(int height, int width)
        {
            Height = height;
            Width = width;
        }

        public override bool Equals(object obj)
        {
            return this.Equals(obj as Rectangle);
        }

        public override int GetHashCode()
        {
            return Height.GetHashCode() ^ Width.GetHashCode();
        }

        public bool Equals(Rectangle r)
        {
            if (ReferenceEquals(r,null))
                return false;

            return ((Height == r.Height) && (Width == r.Width) ||
                    (Height == r.Width) && (Width == r.Height));
        }

        public static bool operator ==(Rectangle r1, Rectangle r2)
        {
            if (ReferenceEquals(r1, null))
            {
                return ReferenceEquals(r2, null) ? true : false;
            }

            return r1.Equals(r2);
        }

        public static bool operator !=(Rectangle r1, Rectangle r2)
        {
            return !(r1 == r2);
        }

        // Result:
        //  < 0 : this instance less than r
        //  = 0 : this instance equivalent to r
        //  > 0 : this instance greater than r
        public int CompareTo(Rectangle r)
        {
            if (ReferenceEquals(r, null))
                return 1;  

            if (this.Equals(r))
                return 0;

            else if (this.Area() == r.Area())
                return this.Width - r.Width;

            else
                return this.Area() - r.Area();
        }

        public static bool operator <(Rectangle r1, Rectangle r2)
        {
            if (ReferenceEquals(r1, null))
                return false;

            else
                return (r1.CompareTo(r2) < 0) ? true : false;
        }

        public static bool operator >(Rectangle r1, Rectangle r2)
        {
            if (ReferenceEquals(r1, null))
                return false;

            else
                return (r1.CompareTo(r2) > 0) ? true : false;
        }

        public static bool operator <=(Rectangle r1, Rectangle r2)
        {
            return (r1 < r2) || (r1 == r2);
        }

        public static bool operator >=(Rectangle r1, Rectangle r2)
        {
            return (r1 > r2) || (r1 == r2);
        }

        public int Area()
        {
            return Height * Width;
        }
    }

#418 – Implementing the IComparable Interface

If you implement a reference type where it makes sense to compare two instances of the type, with one instance greater or lesser than another, you should implement the IComparable interface for your type.  IComparable is used within .NET when sorting elements of a list, e.g. List<T>.Sort.

Here’s an example for a Rectangle type.

        // Result:
        //  < 0 : this instance less than r
        //  = 0 : this instance equivalent to r
        //  > 0 : this instance greater than r
        public int CompareTo(Rectangle r)
        {
            if (this.Equals(r))
                return 0;

            else if (this.Area() == r.Area())
                return this.Width - r.Width;

            else
                return this.Area() - r.Area();
        }

#135 – Implementing IComparable to Allow Sorting a Custom Type

Arrays of elements that belong to a custom type cannot be sorted, unless the type implements the IComparable interface.

To make elements of a custom type sortable, you need to implement IComparable in your type.  IComparable consists of the single method CompareTo, which compares two objects.

Here’s an example of a Person class implementing CompareTo to sort people in LastName/FirstName order:

            public int CompareTo(object obj)
            {
                Person other = obj as Person;
                if (other == null)
                    throw new ArgumentException("Object is not a Person");
                else
                {
                    // Sort by LastName, then by FirstName (ignore case)
                    int compare = this.LastName.ToLower().CompareTo(other.LastName.ToLower());
                    if (compare == 0)
                        compare = this.FirstName.ToLower().CompareTo(other.FirstName.ToLower());

                    return compare;
                }

Here’s an example of sorting an array of Person objects:

            Person[] folks = new Person[4];
            folks[0] = new Person("Bronte", "Emily");
            folks[1] = new Person("Bronte", "Charlotte");
            folks[2] = new Person("Tennyson", "Alfred");
            folks[3] = new Person("Mailer", "Norman");
            Array.Sort(folks);    // C. Bronte, E. Bronte, Mailer, Tennyson