C#: Comparison operator overloading and spaceship operator

Lets consider I have a class Employee which has  Name and JobGrade fields. I want to overload the comparison operators for this class so that it can participate in all types of comparison including <. <=, ==, >=, != and Equals. I want to translate the comparison in-between two Employee objects to be a comparison between the JobGrade . Since I do not want to write the comparison logic each time, typically I'd implement the comparison in one method and from comparison operator overloading methods call this common method. So in C# I'd do something like.

 class Employee{    public Employee(string name, int jobGrade){        Name = name;        JobGrade = jobGrade;    }    public string Name;    public int JobGrade;

     public static bool operator <(Employee emp1, Employee emp2){        return Comparison(emp1, emp2) < 0;    }    public static bool operator >(Employee emp1, Employee emp2){        return Comparison(emp1, emp2) > 0;    }    public static bool operator ==(Employee emp1, Employee emp2){        return Comparison(emp1, emp2) == 0;    }    public static bool operator !=(Employee emp1, Employee emp2){        return Comparison(emp1, emp2) != 0;    }    public override bool Equals(object obj){        if (!(obj is Employee)) return false;        return this == (Employee)obj;    }    public static bool operator <=(Employee emp1, Employee emp2){        return Comparison(emp1, emp2) <= 0;    }    public static bool operator >=(Employee emp1, Employee emp2){        return Comparison(emp1, emp2) >= 0;    }    public static int Comparison(Employee emp1, Employee emp2){        if (emp1.JobGrade < emp2.JobGrade)            return -1;        else if (emp1.JobGrade == emp2.JobGrade)            return 0;        else if (emp1.JobGrade > emp2.JobGrade)            return 1;        return 0;    }}

This is kind of huge as I have to overload each comparison operator individually even though what I want is to just make Employee object comparable to any other Employee object. What happens is as follows

  1. public static bool operator <=(Employee emp1, Employee emp2) gets compiled to a method named bool op_LessThanOrEqual(Employee, Employee). Similiar naming convention is used for the other operators.
  2. On seeing the code emp1 <= emp2 the compiler emits code to call the method op_LessThanOrEqual

This makes overloading operators individually a requirement.

What if

In some languages like Ruby and also Perl (I am not that sure on perl) there is a concept of space ship operator <=>. This operator must return less than 0, equal to 0, greater than 0 based on whether the expression on the left is less than, equal-to or greater than that on the right (similiar to IComparable:CompareTo). If C# compiler supports the concept of the space-ship operator then we can simply overload this one operator and expect the compiler to emit code to call this operator for all comparison. So if C# supports this then the above code would look like

 class Employee{    public Employee(string name, int jobGrade){        Name = name;        JobGrade = jobGrade;    }    public string Name;    public int JobGrade;     public override bool Equals(object obj){        if (!(obj is Employee)) return false;        return this == (Employee)obj;    }    // same in the lines of IComparable:CompareTo
    publicstaticintoperator  <=>(Employee emp1, Employee emp2) {        if (emp1.JobGrade < emp2.JobGrade)            return -1;        else if (emp1.JobGrade == emp2.JobGrade)            return 0;        else if (emp1.JobGrade > emp2.JobGrade)            return 1;        return 0;    }}

In this case what'll happen is as follows

  1. public static int operator <=>(Employee emp1, Employee emp2) gets compiled to bool op_SpaceShip(Employee, Employee)
  2. On seeing emp1 <= emp2 the compiler emits code to call the method op_SpaceShip(emp1, emp2) <= 0
  3. On seeing emp1 != emp2 the compiler emits code to call the method op_SpaceShip(emp1, emp2) != 0
  4. Or generically emp1 op emp2 gets compiled to op_SpaceShip(emp1, emp2) op 0

Interfaces like IComparable already exists which needs the class to implement CompareTo which works exactly like <=> operator overloaded as above. If only the compiler directly made calls to this then the need for one to implement this interface and then make calls to this method gets removed.

The overloaded <=> acts as a filler. In case <= and => are already overloaded for a class then calls are made to these methods and for operators not overloaded like == and != calls are made to <=>.

Its not important (atleast to me) how we achieve this and can include ways like have a method CompareTo in Object in the same lines of Equals and make the compiler emits calls to it based on the operator getting used or use explicite <=> operator overloading. Either way the need to overload all 5 operators should be eliminated.

Comments

  • Anonymous
    October 11, 2005
    space ship operator: This is something that should defintiely be implemented in c# but I think the compiler should unconditionaly create 2 3 and 4.

  • Anonymous
    October 11, 2005
    The comment has been removed

  • Anonymous
    October 11, 2005
    Yes, Perl has a spaceship operator. (Also 'cmp', the spaceship equivalent for strings, to go with 'eq', 'lt', etc.)

  • Anonymous
    November 11, 2005
    Just had geat fun with this and Generics.

    Consider the simple methods

    public bool ArrayContains<T>(T[] array, T value) where T:class
    {
    return array != null &&
    Array.Exists(array, delegate(candidate) {
    return candidate == value;
    });
    }

    public void ExcludeAll<T>(ref T[] array,
    T value) where T:class
    {
    List<T> list = new List<T>();
    if(array != null)
    {
    foreach(T t in array)
    {
    if (t != value) list.Add(t);
    }
    array = t.ToArray();
    }
    }

    That "where T:class" raised alarm bells. The code won't compile without it, but the method as given should surely work if T is an int?

    Now consider the case where T is string. The code fails. I think the reason is that because the "operator ==" implementations are defined as requiring the variables to be known to be string at compile time. This is not resolved at JIT time or whenever string gets substituded for T.

    Based on your code above, I'd expect the following to fail:

    object a = "Hello";
    object b = "Hello";
    bool shouldBeTrue = (a == b);

    where

    string a = "Hello";
    string b = "Hello";
    bool isTrueThisTime = (a == b);

    because the operator overload workes like "new" in place of "override". The variable has to be the right type. It's not simple polymorphism where only the type of the object pointed to counts. I wanted polymorphic behaviour so using .Equals instead makes more sense.

    The solution for my code problem, after much fighting a very upset debugger, was to replace == with .Equals(). All in all, a subtle problem.

    Want polymorphic equality checking? Use .Equals().

    Want non-polymorphic equality checking use ==.

  • Anonymous
    December 20, 2005
    I bet it's possible come up with a snippet that would fill out the first example for you (minus the Compare method), instead of waiting for the compiler to support it.

  • Anonymous
    February 26, 2006
    Hi, Nice article.

    Just tell me thus this method (CompareTo(object)) is called from the external sorting method too for sorting items of our class ?? For ex: In datagrid auto-sorting case???

    Thanks
    Abhi Win
    abhi.win@gmail.com

  • Anonymous
    May 16, 2006
    The comment has been removed

  • Anonymous
    May 22, 2006
    Here's a way to implement the "spaceship" algorithm that's intuitive rather than exhaustively comparative:

    public static int operator <=>(Employee emp1, Employee emp2)
    {
    return (emp2.JobGrade<emp1.JobGrade)-(emp1.JobGrade<emp2.JobGrade);
    }

    Returns the same values, but only makes two comparisons.

  • Anonymous
    August 01, 2006
    What happen with your code when you do something like this:
    if( myEmployee == null)
    {
      .....
    }

    this is the same as calling
    Comparison(myEmployee, null). I think this will crash when it tries to do emp2.JobGrade because emp2 is null.

  • Anonymous
    June 07, 2007
    Inserting the following at the top of the Comparison function will handle the null issue (or anyone else stumbling across this via google)...

    if ((object)day1 == null &amp;&amp; (object)day2 == null) return 0;else if ((object)day1 == null) return -1;else if ((object)day2 == null) return 1;
    ...note, casting to "object" is done to avoid infinite recursion (else they would continue to call the same comparison operators, and an object cast works just fine for testing against null).

  • Anonymous
    August 23, 2007
    PingBack from http://damieng.com/blog/2005/10/11/automaticcomparisonoperatoroverloadingincsharp

  • Anonymous
    March 18, 2009
    Really, you only need to have this: return emp1.JobGrade <=> emp2.JobGrade; No need for all those else ifs.

  • Anonymous
    November 12, 2009
    Hey can you tell me how to compare two list objects in c#.net

  • Anonymous
    March 23, 2010
    Here is a solution that might work:  define a class to store the two objects, add these to a List, and then call List.Sort with a delegate that compares the field you want to sort by:   http://www.developerfusion.com/code/5513/sorting-and-searching-using-c-lists/

  • Anonymous
    July 22, 2010
    am overloading ">" operator class complex{ //friend bool operator> (complex &op1, complex &op2); friend istream &operator>>(istream&,complex &); private: double r; double i; public: complex(){ r=1; i=1; } complex(double real,double img){ r=real; i=img; } complex(complex &obj){ r=obj.r; i=obj.i; } void display(){ cout<<"("<<r<<","<<i<<")"; } bool operator>( complex &op2){      return r>op2.r; } }; istream &operator>>(istream&obj,complex &obj1){ obj>>obj1.r; obj>>obj1.i; return obj; } //////////////////////////////////////////// void main(){ clrscr(); complex c1(4,5); complex c2(2,3);       if (c1>c2) cout<<"c1 is greater";       else cout<<"c2 is greater"; getch(); } i have tryd this code in different ways but still facing errors. 1-> type name expected. 2->declaration missing 3-> illegal structure operation. plz help me out.

  • Anonymous
    September 23, 2015
    Yes, this is a necromancer post.... One reason for requiring verbose overloading of comparison operators is that the type might not have "consistent" comparison behaviour. However, by the same token, if a particular type is supposed to have consistent comparison behaviour, it is now possible to make it inconsistent due to an implementation bug. If I'm not mistaken, smalltalk has the concept of defining 2 comparisons and deriving the rest. E.g. By defining == and > with boolean results, you can derive the rest as follows: !=   is the same as   !(==) <   is the same as   !(==) && !(>) >=  is the same as  > || == <=  is the same as  !(>)