Welcome to MSDN Blogs Sign in | Join | Help

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.

Published Tuesday, October 11, 2005 4:11 PM by abhinaba
Filed under:

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

# re: C#: Comparison operator overloading and spaceship operator

Tuesday, October 11, 2005 10:56 AM by Hasani
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.

# re: C#: Comparison operator overloading and spaceship operator

Tuesday, October 11, 2005 6:43 PM by Damien Guard
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

# re: C#: Comparison operator overloading and spaceship operator

Wednesday, October 12, 2005 6:15 AM by Jesse McGrew
Yes, Perl has a spaceship operator. (Also 'cmp', the spaceship equivalent for strings, to go with 'eq', 'lt', etc.)

# re: C#: Comparison operator overloading and spaceship operator

Friday, November 11, 2005 10:58 AM by Richard Corfield
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 ==.

# re: C#: Comparison operator overloading and spaceship operator

Tuesday, December 20, 2005 5:37 PM by Eileen
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.

# re: C#: Comparison operator overloading and spaceship operator

Monday, February 27, 2006 3:04 AM by Abhi Win
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

# re: C#: Comparison operator overloading and spaceship operator

Tuesday, May 16, 2006 6:33 PM by Brian Fink
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.

# re: C#: Comparison operator overloading and spaceship operator

Monday, May 22, 2006 5:19 PM by Brian Fink
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.

# re: C#: Comparison operator overloading and spaceship operator

Tuesday, August 01, 2006 8:25 AM by Mike
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.

# handling nulls

Thursday, June 07, 2007 7:59 PM by Travis Johnson

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

# Automatic comparison operator overloading in C# at DamienG

# re: C#: Comparison operator overloading and spaceship operator

Wednesday, March 18, 2009 12:41 PM by Tom

Really, you only need to have this:

return emp1.JobGrade <=> emp2.JobGrade;

No need for all those else ifs.

Leave a Comment

(required) 
required 
(required) 
 
Page view tracker