What purpose does the Comparer
class serve if the type that you specify already implements IComparable
?
If I specify Comparer.Defaul
I think your question is why have a base class that only seems to have one useful method which happens to be the same method you'd implement if you implemented the interface directly. If I understood that correctly I guess you're right that there's not much benefit to deriving from Comparer<T>
as opposed to implementing IComparer<T>
directly except that the base class provides you with a generic and non-generic implementation with a single overridden method.
But if your question is why have both IComparer<T>
and IComparable<T>
, then
as others have indicated, Comparer<T>
allows you to define different ways to perform the comparison. It is an implementation of the Strategy design pattern. A great example of this would be the various StringComparer
properties such as StringComparer.Ordinal, StringComparer.OrdinalIgnoreCase, etc. This allows you to sort strings differently under different circumstances which the IComparable<T>
interface simply cannot anticipate.
But in addition to being able to re-define the way a comparison is performed, sometimes providing an external comparer is the only way you can do it. For example, the Windows Forms ListView class allows you to specify an IComparer for its sort logic. But ListViewItem does not implement IComparable. So without a comparer strategy that knows how to do it, ListViewItem is not sortable because it has no default IComparable implementation.
So at the end of the day, it's just another extensibility point that allows you more flexibility when you're not the author of the type that is to be sorted (or you need multiple sorting strategies.)
Perhaps an example would help. Let's say you're writing an extension method that checks to see if a given value is between a range.
public static bool Between<T>(this T value, T minValue, T maxValue) {
var comparer = Comparer<T>.Default;
int c1 = comparer.Compare(value, minValue);
int c2 = comparer.Compare(value, maxValue);
return (c1 >= 0 && c2 <= 0);
}
In this case, I don't know anything about the type T. It may implement IComparable
or it may implement IComparable<T>
or it may implement neither and an exception will be thrown. This also allows me to easily add an overload for this method that lets the caller pass in their own comparer. But Comparer comes in handy here because it lets me get a default comparer for an unknown type that may or may not implement a generic or non-generic IComparable interface.
Comparer<T>
holds an actual compare method. It can be used if you want to compare the object differently than in your IComparable
implementation.
If a type implements IComparable<T>
, it is almost certainly better to use that than IComparable
. With value types, the performance of IComparable<T>
is often much better than that of the non-generic IComparable
. With inheritable reference types, IComparable<T>
can offer better semantics than IComparable
by allowing rankings based upon derived-type fields.
For an example of the latter benefit, suppose one has an abstract base class ScheduleEvent
, with a property EventTime
, that implements IComparable<ScheduleEvent>
by sorting EventTime
. Derived types include ScheduledPopupMessageEvent
with a message string, a ScheduledGongEvent
with a GongVolume
parameter. Multiple ScheduleEvent
s with the same EventTime
must report zero for IComparable<ScheduleEvent>.CompareTo
, because there is no safe and consistent way to rank ScheduleEvent
s of different types, and because two ScheduleEvent
s which both report themselves as unranked relative to a third must, for consistency, report themselves as unranked relative to each other. On the other hand, there would be no problem with having ScheduledGongEvent
implementing IComparable<ScheduledGongEvent>
take GongVolume
into account as well as EventTime
, or with ScheduledPopupMessageEvent
doing likewise with its Message parameter.
It is useful, then, to have things like sorting routines use IComparable<T>
if it exists, but be able to fall back to IComparable
if IComparable<T>
does not exist. Checking for whether a class implements IComparable<T>
and selecting an appropriate implementation if it does, however, is a little expensive. Fortunately, once a type is determined to have an IComparable<T>
implementation, it can be relied upon to always have one; likewise, if a type is found not to have such an implementation, it never will. Further, if a generic class has any static fields, every combination of type parameters will yield a different class with different fields. Thus, the first time Comparer<T>.Default
is run with a particular type parameter T, it will store the Comparer routine it returns into a static field. If Comparer<T>
is run again with that same type, it will return the same comparer routine. Although it might seem odd to have a static Comparer<T>
class with just one method, creating a separate Comparer<T>
class for every type T
provides a place to store the created compare routine.
public class Person
{
public string LastName;
public string FirstName;
}
public class Class2
{
public void test()
{
List<Person> classList = new List<Person>();
//add some data to the list
PersonComparer comp = new PersonComparer();
classList.Sort(comp);
}
}
public class PersonComparer : Comparer<Person>
{
public override int Compare(Person x, Person y)
{
int val = x.LastName.CompareTo(y.LastName);
if (val == 0)
{
val = x.FirstName.CompareTo(y.FirstName);
}
return val;
}
}
Because you need sometimes keep sets/ordered queues ordered by something else then 'natural' order or more then one natural order exists.
For example if you have plane lines you may want to sort it by:
Tasks in computer can be scheduled by:
So even in one application you may need to sort the objects by different properties. You cannot do this by int compareTo(Object)
method as it cannot differentatie between contexts. However you can add the context i.e. implement CompareByPriority
.
The type doesn't need to implement IComparable, it can be any type - there are no constrains on T
:
public abstract class Comparer<T> : IComparer, IComparer<T>
The new Comparer
you create implements IComparer<T>
and the non-generic IComparer
, and can be used for comparisons and sorting of collections.
You are correct: if your type, Customer
implements IComparable
, and you don't need another comparison, Comparer
isn't useful to you. Most classes in the .net framework can accept both IComparable<T>
or Comparer<T>
, so you can use either one.
However, you are wrong to assume that is always the case. It is very possible to create a Comparer
for a non-Comparable type. Note that the following is not required:
public abstract class Comparer<T> : IComparer, IComparer<T>
where T : IComparable, IComparable<T>
Suppose you have a simple class, Person
and you want to sort a list of Persons
, your best bet it to write a Comparer:
public class Person
{
string Name { get; set; }
}