views:

450

answers:

5

When I want to constraint the type T to be comparable, should I use:

where T : IComparable

or

where T : IComparable<T>

I can't get my head around if #2 makes sense. Anyone can explain what the difference would be?

+2  A: 

The IComparable<T> allows the comparator to be strongly typed.

You can have

public int CompareTo(MyType other)
{
    // logic
}

as oppose to

public int CompareTo(object other)
{
    if (other is MyType)
        // logic
}

Take for instance the next example witch implements both interfaces:

public class MyType : IComparable<MyType>, IComparable
{
    public MyType(string name, int id)
    { Name = name; Id = id; }

    public string Name { get; set; }
    public int Id { get; set; }

    public int CompareTo(MyType other)
    {
        if (null == other)
            throw new ArgumentNullException("other");
        return (Id - other.Id > 0 ? 1 : 0);
    }

    public int CompareTo(object other)
    {
        if (null == other)
            throw new ArgumentNullException("other");
        if (other is MyType)
            return (Id - (other as MyType).Id > 0 ? 1 : 0);
        else
            throw new InvalidOperationException("Bad type");
    }
}


MyType t1 = new MyType("a", 1);
MyType t2 = new MyType("b", 2);
object someObj = new object();

// calls the strongly typed method: CompareTo(MyType other)
t1.CompareTo(t2);
// calls the *weakly* typed method: CompareTo(object other)
t1.CompareTo(someObj);

If MyType was only implemented with IComparable<MyType>, the second compareTo(someObj) is a compile time error. This is one advantage of strongly typed generics.

On the other hand, there are methods in the framework that require the non generic IComparable like Array.Sort. In these cases, you should consider implementing both interfaces like in this example.

bruno conde
bruno: in your code, I would rather delegate the comparison job in CompareTo(object) to CompareTo(MyType) in the second branch of CompareTo(object), rather than duplicate the code.
Steve
+4  A: 

The main difference between IComparable and IComparable<> is that the first is pre-generics so allows you to call the compare method with any object, whereas the second enforces that it shares the same type:

IComparable - CompareTo(object other);
IComparable<T> - CompareTo(T other);

I would go with the second option provided that you don't intend to use any old .net 1.0 libraries where the types may not implement the modern, generic solution. You'll gain a performance boost since you'll avoid boxing and the comparisons won't need to check the types match and you'll also get the warm feeling that comes from doing things in the most cutting edge way...


To address Jeff's very good and pertinent point I would argue that it is good practice to place as few constraints on a generic as is required to perform the task. Since you are in complete control of the code inside the generic you know whether you are using any methods that require a basic IComparable type. So, taking his comment into consideration I personally would follow these rules:

  • If you are not expecting the generic to use any types that only implement IComparable (i.e. legacy 1.0 code) and you are not calling any methods from inside the generic that rely on an IComparable parameter then use the IComparable<> constraint only.

  • If you are using types that only implement IComparable then use that constraint only

  • If you are using methods that require an IComparable parameter, but not using types that only implement IComparable then using both constraints as in Jeff's answer will boost performance when you use methods that accept the generic type.

To expand on the third rule - let's assume that the class you are writing is as follows:

public class StrangeExample<T> where ... //to be decided
{
    public void SortArray(T[] input)
    {
         Array.Sort(input);
    }

    public bool AreEqual(T a, T b)
    {
        return a.CompareTo(b) == 0;
    }
}

And we need to decide what constraints to place on it. The SortArray method calls Array.Sort which requires the array that is passed in to contains objects that implement IComparable. Therefore we must have an IComparable constraint:

public class StrangeExample<T> where T : IComparable

Now the class will compile and work correctly as an array of T is valid for Array.Sort and there is a valid .CompareTo method defined in the interface. However, if you are sure that you will not want to use your class with a type that does not also implement the IComparable<> interface you can extend your constraint to:

public class StrangeExample<T> where T : IComparable, IComparable<T>

This means that when AreEqual is called it will use the faster, generic CompareTo method and you will see a performance benefit at the expense of not being able to use it with old, .NET 1.0 types.

On the other hand if you didn't have the AreEqual method then there is no advantage to the IComparable<> constraint so you may as well drop it - you are only using IComparable implementations anyway.

Martin Harris
This isn't entirely right as some elements of the BCL only operate with IComparable and not IComparable<T>, such as Array.Sort and ArrayList.Sort. Really, the constraint should enforce both interfaces.
Jeff Yates
Thanks Martin. Can you please explain the last rule you wrote? Do you mean performance is gonna be better if I have both interfaces and use the generic comparison?
Joan Venge
Yes, since the main advantage you get from using the IComparable<> constraint is a performance benefit if you *must* support the IComparable type because you are using methods that require it (such as Array.Sort) but you *can* support the IComparable<> type as you aren't using any legacy types that don't implement it then you are best to require both and use the object as the faster type where you can.
Martin Harris
A: 

I would use the second constraint as that will allow you to reference strongly-typed members of the interface. If you go with your first option then you will have to cast to use the interface type.

Andrew Hare
+1  A: 

Those are two different interfaces. Before .NET 2.0 there were no generics, so there was just IComparable. With .NET 2.0 came generics and it became possible to make IComparable<T>. They do exactly the same. Basically IComparable is obsolete, although most libraries out there recognize both.

To make your code really compatible, implement both, but make one call the other, so you don't have to write the same code twice.

Vilx-
+5  A: 

You may want both constraints, as in:

where T : IComparable, IComparable<T>

This would make your type compatible with more users of the IComparable interfaces. The generic version of IComparable, IComparable<T> will help to avoid boxing when T is a value type and allows for more strongly typed implementations of the interface methods. Supporting both ensures that no matter which interface some other object asks for, your object can comply and therefore inter-operate nicely.

For example, Array.Sort and ArrayList.Sort use IComparable, not IComparable<T>.

Jeff Yates