Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Advantages/Disadvantages of different implementations for Comparing Objects

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?
like image 441
Kevin Crowell Avatar asked Mar 21 '10 18:03

Kevin Crowell


People also ask

How do you compare two objects?

The equals() method of the Object class compare the equality of two objects. The two objects will be equal if they share the same memory address. Syntax: public boolean equals(Object obj)


3 Answers

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 for List<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 a Comparison<T> delegate for that method (at least not directly).

If you really wanted to use Comparison<T> for both, you could make a generic IComparer<T> implementation that took a Comparison<T> delegate in its constructor, and implemented IComparer<T>.

public class ComparisonComparer<T> : IComparer<T>
{
    private Comparison<T> method;
    public ComparisonComparer(Comparison<T> comparison)
    {
       this.method = comparison;
    }

    public int Compare(T arg1, T arg2)
    {
        return method(arg1, arg2);
    }
}
like image 198
Reed Copsey Avatar answered Oct 31 '22 17:10

Reed Copsey


Probably the biggest advantage to accepting a Comparison<T> as opposed to an IComparer<T> is the ability to write anonymous methods. If I have, let's say, a List<MyClass>, where MyClass contains an ID property that should be used for sorting, I can write:

myList.Sort((c1, c2) => c1.ID.CompareTo(c2.ID));

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). The Comparer<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:

public class AnonymousComparer<T> : IComparer<T>
{
    private Comparison<T> comparison;

    public AnonymousComparer(Comparison<T> comparison)
    {
        if (comparison == null)
            throw new ArgumentNullException("comparison");
        this.comparison = comparison;
    }

    public int Compare(T x, T y)
    {
        return comparison(x, y);
    }
}

This allows writing code such as:

myList.BinarySearch(item,
    new AnonymousComparer<MyClass>(x.ID.CompareTo(y.ID)));

It's not exactly pretty, but it saves some time.

Another useful class I have is this one:

public class PropertyComparer<T, TProp> : IComparer<T>
    where TProp : IComparable
{
    private Func<T, TProp> func;

    public PropertyComparer(Func<T, TProp> func)
    {
        if (func == null)
            throw new ArgumentNullException("func");
        this.func = func;
    }

    public int Compare(T x, T y)
    {
        TProp px = func(x);
        TProp py = func(y);
        return px.CompareTo(py);
    }
}

Which you can write code designed for IComparer<T> as:

myList.BinarySearch(item, new PropertyComparer<MyClass, int>(c => c.ID));
like image 38
Aaronaught Avatar answered Oct 31 '22 17:10

Aaronaught


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.

like image 40
Ron Klein Avatar answered Oct 31 '22 18:10

Ron Klein