views:

23

answers:

3

Hi all,

consider the following basic class layout:

public class Base : IComparable<Base>
{
  public int CompareTo(Base other)
  {
    //Do comparison
  }
}

public class Derived : Base, IComparable<Derived>
{
  public int CompareTo(Derived other)
  {
    //Do comparison
  }  
}

public class BaseComparer : IComparer<Base>
{
  public int Compare(Base x, Base y)
  {
   return x.CompareTo(y);
  }
}

and then using those as follows:

List<Base> thingies = new List<Base>
{
  new Base(),
  new Derived(),
  new Derived()
};

thingies.Sort(new BaseComparer());

I was expecting the Comparer to be calling the Derived.CompareTo method in those situations where both it's x and y parameters are Derived instances.

However, this is not the case and Base.CompareTo is called instead and I keep wondering why. I can't seem to deduct this behaviour with my basic understanding of the overload resolution rules as described in the C# language specification.

Can someone shed some light on this for me?

+5  A: 

Base knows nothing of its derived classes – so in Base there’s only one CompareTo method, and that gets called unconditionally.

The point is that overload resolution happens at compile time where there’s no information about the actual type of Base references available. You need to override the method in Derived, not overload it:

public class Derived : Base
{
  public override int CompareTo(Base other)
  {
    //Do comparison
  }  
}

And additionally mark the Base.CompareTo method virtual.

Notice that this doesn’t implement IComparable<Derived> any more. You can also do this, but for your purpose that’s unrelated.

Konrad Rudolph
Thanx for your effort. I was trying to be clever and prevent the virtual/override implementation on CompareTo(Base) by using the overload for the specific scenario where both x and y are Derived instances. Seems I was not so clever after all :)
Anton
+1  A: 

Overload resolution is not what's happening here. You've got two independent methods: their full names are IComparable<Base>.CompareTo and IComparable<Derived>.CompareTo.

The only one that BaseComparer knows how to call is IComparable<Base>.CompareTo. It knows nothing about IComparable<Derived>.

In your application, does it make sense to compare a Base with a Derived -- that is, can say that a Base comes before or after a Derived?

  • If so, you'd be better of to stay with only IComparable<Base>, or even the non-generic IComparable, and be prepared to check types in subclasses
  • If not, you should consider making Base abstract, and only implementing IComparable<T> on leaf classes
Tim Robinson
So I see now. In my case it makes sense to compare Base and Derived instances. I was trying to be clever and only have to a write a separate CompareTo implementation in the case where a more subtle comparison must take place (that is, where both are Derived instances) and for all other scenarios simply let overload resolution fall back on the sufficient comparison in Base.CompareTo. Alas... :)
Anton
+1  A: 

IComparable<Base> and IComparable<Derived> are two different types, so two methods CompareTo in Derived are mapped onto two different slots. CompareTo invoked by BaseComparer calls method of IComparable<Base>. You can denote CompareTo(Base) in Base as virtual and override it in Derived to get (partially) expected behavior.

public class Base : IComparable<Base>
{
    public virtual int CompareTo(Base other)
    {
        // do comparison
    }
}

public class Derived : Base, IComparable<Derived>
{
    public int CompareTo(Derived other)
    {
        // do comparison
    }

    public override int CompareTo(Base other)
    {
        if (other is Derived)
            return CompareTo((Derived) other);
        return base.CompareTo(other);
    }
}
desco