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

public static int operator <=>(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 (16)

  1. Hasani says:

    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.

  2. Anonymous says:

    This got me thinking and I’ve knocked up a class that implements this for you providing you can inherit from it.

    http://damieng.blogspot.com/2005/10/automatic-comparison-operator.html

  3. Anonymous says:

    Yes, Perl has a spaceship operator. (Also ‘cmp’, the spaceship equivalent for strings, to go with ‘eq’, ‘lt’, etc.)

  4. Anonymous says:

    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 ==.

  5. Anonymous says:

    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.

  6. Anonymous says:

    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

  7. Anonymous says:

    I think the reason why a spaceship operator is not included in c#, for your particular implementation at least, is that stepping through a list of truth tests until you find one that matches your case robs the program of performance. On the other hand, if each case of the comparison operator is overloaded individually, the only code called will be the code for the particular comp op that you specified, providing much cleaner execution. True, the required code is more verbose, but that’s the price we pay sometimes for speed.

  8. Anonymous says:

    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.

  9. Anonymous says:

    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.

  10. Anonymous says:

    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).

  11. Tom says:

    Really, you only need to have this:

    return emp1.JobGrade <=> emp2.JobGrade;

    No need for all those else ifs.

  12. Bala Balaji Raju says:

    Hey can you tell me how to compare two list objects in c#.net

  13. Eric says:

    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/

  14. Urfa says:

    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.

  15. Craig says:

    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  !(>)