This questions involves 2 different implementations of essentially the same code.
First, using delegate to create a Comparison method that can be used as a parameter when sorting a collection of objects:
class Foo
{
public static Comparison<Foo> BarComparison = delegate(Foo foo1, Foo foo2)
{
return foo1.Bar.CompareTo(foo2.Bar);
};
}
I use the above when I want to have a way of sorting a collection of Foo objects in a different way than my CompareTo function offers. For example:
List<Foo> fooList = new List<Foo>();
fooList.Sort(BarComparison);
Second, using IComparer:
public class BarComparer : IComparer<Foo>
{
public int Compare(Foo foo1, Foo foo2)
{
return foo1.Bar.CompareTo(foo2.Bar);
}
}
I use the above when I want to do a binary search for a Foo object in a collection of Foo objects. For example:
BarComparer comparer = new BarComparer();
List<Foo> fooList = new List<Foo>();
Foo foo = new Foo();
int index = fooList.BinarySearch(foo, comparer);
My questions are:
- What are the advantages and disadvantages of each of these implementations?
- What are some more ways to take advantage of each of these implementations?
- Is there a way to combine these implementations in such a way that I do not need to duplicate the code?
- Can I achieve both a binary search and an alternative collection sort using only 1 of these implementations?
They really address different needs:
IComparable
is useful for objects that are ordered. Real numbers should be comparable, but complex numbers cannot - it is ill-defined.IComparer
allows to define re-usable, well-encapsulated comparers. This is especially useful if the comparison needs to know some additional information. For example, you might want to compare dates and times from different time zones. That can be complicated, and a separate comparer should be used for this purpose.A comparison method is made for simple comparison operations that are not complicated enough for reusability to be of any concern, e.g. sorting a list of customers by their first name. This is simple operation, hence does not need additional data. Likewise, this is not inherent to the object, because the objects are not naturally ordered in any way.
Lastly, there is
IEquatable
, which might be important if yourEquals
method can only decide if two objects are equal or not, but if there is no notion of 'larger' and 'smaller', e.g. complex numbers, or vectors in space.Probably the biggest advantage to accepting a
Comparison<T>
as opposed to anIComparer<T>
is the ability to write anonymous methods. If I have, let's say, aList<MyClass>
, whereMyClass
contains anID
property that should be used for sorting, I can write:Which is a lot more convenient than having to write an entire
IComparer<MyClass>
implementation.I'm not sure that accepting an
IComparer<T>
really has any major advantages, except for compatibility with legacy code (including .NET Framework classes). TheComparer<T>.Default
property is only really useful for primitive types; everything else usually requires extra work to code against.To avoid code duplication when I need to work with
IComparer<T>
, one thing I usually do is create a generic comparer, like this:This allows writing code such as:
It's not exactly pretty, but it saves some time.
Another useful class I have is this one:
Which you can write code designed for
IComparer<T>
as:There really is no advantage to either option in terms of performance. It's really a matter of convenience and code maintainability. Choose the option you prefer. That being said, the methods in question limit your choices slightly.
You can use the
IComparer<T>
interface forList<T>.Sort
, which would allow you to not duplicate code.Unfortunately, BinarySearch does not implement an option using a
Comparison<T>
, so you cannot use aComparison<T>
delegate for that method (at least not directly).If you really wanted to use
Comparison<T>
for both, you could make a genericIComparer<T>
implementation that took aComparison<T>
delegate in its constructor, and implementedIComparer<T>
.In your case, advantage of having an
IComparer<T>
overComparision<T>
delegate, is that you can also use it for the Sort method, so you don't need aComparison
delegate version at all.Another useful thing you can do is implementing a delegated
IComparer<T>
implementation like this:and a more advanced version:
The delegate technique is very short (lambda expressions might be even shorter), so if shorter code is your goal, then this is an advantage.
However, implementing the IComparer (and its generic equivalent) makes your code more testable: you can add some unit testing to your comparing class/method.
Furthermore, you can reuse your comparer implementation when composing two or more comparers and combining them as a new comparer. Code reuse with anonymous delegates is harder to achieve.
So, to sum it up:
Anonymous Delegates: shorter (and perhaps cleaner) code
Explicit Implementation: testability and code reuse.