/ comparisoninterfacesort

Relative Ordering with IComparable and CompareTo

Someone asked a question on StackOverflow about the difference between the int CompareTo method and an ordinary If condition. As it turns out, the poster’s if statement is exactly how Int32 implements CompareTo.

The IComparable and IComparable<T> interfaces each have only a single method, which can be used to compare two objects for the purpose of determining how they should be ordered relative to one another.

 // IComparable<T> - Compares the current object with another object of the same type.
int CompareTo(T other);

 // IComparable - Compares the current object with another object.
int CompareTo(object obj);

The return value, an int, indicates the relative order:

  • < 0: This object is less than the other parameter.
  • = 0: This object is equal to the other object.
  • 0: This object is greater than the other object.

All primitive types implement these interfaces, and the result of CompareTo is fairly intuitive in most cases. Following are the details of how each type implements IComparable<T>. (Note that m_value represents the value of the current integer or double, and value is the other integer or double being compared to the current value.)

Int32:

if (m_value < value)
    return -1;
if (m_value > value)
    return 1;
return 0;

By far, the most straight-forward. The other x-bit types are implemented the same way (Int64 (long), Int16 (short) and byte).

Double:

if (m_value < value)
    return -1;
if (m_value > value)
    return 1;
if (m_value == value)
    return 0;
 
if (IsNaN(m_value))
    return (IsNaN(value) ? 0 : -1);
else
    return 1;

Note the IsNaN test. If you try to act on a non-number such as the result of 0/0, then <, > and == do not apply, and logic drops down to IsNaN. Read more about IsNaN.

Preference is given to the object that is a valid number. If the double you’re comparing is a valid number, then the double you’re comparing to must be invalid and it treats the first as greater.

If the double you’re comparing is invalid, logic drops through to the last line. If the double you’re comparing to is valid, the first is treated as less. If they’re both invalid, they’re considered equal for the sake of this test (although technically two non-numbers are not equal).

The implementation of Single (also a floating point type) is the same.

Decimal:

Decimal.FCallCompare(ref this, ref value);

This one calls a method that is marked with MethodImplOptions.InternalCall. The only reference I could find was from the knowledgeable Hans Passant on SO. The code is buried in some C++ files within the CLR. Some basic testing shows that the comparison is the same as Int32.

Byte:

return (m_value - value);

The implementation here is basically the same as with Int32 – it performs an arithmetic operation on the values. If the result is 0, then they must be the same byte.

Char:

return (m_value-value);

Since the default behavior of using an arithmetic operator on a char type is to convert it to an integer first, the implementation here is the same as the others.

Bool:

if (m_value == value)
    return 0;
else if (m_value == false)
    return -1;
return 1;

false is considered “less than” true.

DateTime:

long valueTicks = value.InternalTicks;
long ticks = InternalTicks;
if (ticks > valueTicks)
    return 1;
if (ticks < valueTicks)
    return -1;
return 0;

Compares using the Ticks of the two DateTime instances, similar to Int64. It's a little interesting that the Ticks values are assigned to temporary variables prior to the comparison. I did some research but couldn't find anything explaining why this is. If you know why, please leave a comment!

String:

if (strB == null)
    return 1;
return CultureInfo.CurrentCulture.CompareInfo.Compare(this, strB, CompareOptions.None);

If the string being compared to is null, then the first string is considered "greater than" the second. Otherwise, logic drops into CompareInfo.Compare, where there's the following comment:

Our paradigm is that null sorts less than any other string and that two nulls sort as equal.

The method found there is longer then what I've included below, but here's the relevant part which treats null as less than any value, and treats two null values as equal.

if (string1 == null)
{
    if (string2 == null)
        return 0;     // Equal
    return -1;        // null < non-null
}
if (string2 == null)
    return 1;         // non-null > null

The above types implement IComparable as well, and it's nearly identical to IComparable<T> in each case. The only significant difference is that, since the parameter is an object and not a primitive type, an initial check returns 1 if the parameter is null, and if the parameter is not the same primitive type and ArgumentException is thrown. Here's the implementation from Int32:

if (value == null)
    return 1;
if (!(value is int))
    throw new ArgumentException(Environment.GetResourceString("Arg_MustBeInt32"));

You can implement IComparable<T> in your own class as well. The terms "less than", "equal to" and "greater than" are completely subjective, and you determine what they mean in the context of ordering two instances of your own object relative to one another.

Here's a quick sample class, in which I've implemented both interfaces using similar logic to the primitive types. One MySampleClass object is greater than another if it's Id value is greater.

public class MySampleClass : IComparable, IComparable<mysampleclass>
{
    public int Id { get; set; }
    public int CompareTo(object obj)
    {
        if (obj == null)
            return 1;
        if (!(obj is MySampleClass))
            throw new ArgumentException("Argument must be an instance of MySampleClass");
        var otherId = ((MySampleClass)obj).Id;
        if (Id > otherId)
            return 1;
        if (Id < other.Id)
            return -1;
        return 0;
    }
}

Grant Winney

Grant Winney

I write when I've got something to share - a personal project, a solution to a difficult problem, or just an idea. We learn by doing and sharing. We've all got something to contribute.

Read More