Method resolution in Comparer
consider the following basic class layout:
public class Base : IComparable<Base>
{
public int CompareTo(Base other)
{
//Do comparison
}
}
public class Derived : Base, IComparable<De开发者_如何学运维rived>
{
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?
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.
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-genericIComparable
, and be prepared to check types in subclasses - If not, you should consider making
Base
abstract, and only implementingIComparable<T>
on leaf classes
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);
}
}
精彩评论