Click here to Skip to main content
15,883,901 members
Articles / Programming Languages / C#
Article

Generic IComparer

Rate me:
Please Sign up or sign in to vote.
2.94/5 (7 votes)
30 Dec 20052 min read 85K   435   16   3
An article on implementing a generic comparer.

Sample Image

Introduction

A great article by Ryan Beesley on this site demonstrates how to sort a collection of objects using multiple IComparer classes. This is a common way of sorting data, and you will find several articles on this subject. I will show you how to sort a collection on multiple properties, using a generic IComparer class.

Background

Basically, implementing sort on a collection requires two actions:

  1. Creating a class which implements the IComparer interface, and manage, in the Compare method, the way objects has to be compared.
  2. Overloading the Sort method on the collection, and using the IComparer class defined on step 1.

The problem with this approach is that you have to create as much classes than there are properties to compare. Using Reflection allows you to create only one comparer class which will manage all possibilities!

Using the code

First, we will create a collection, and the multiple objects that this collection will hold. I've used the same architecture that Ryan Beesley used in his article, so you can easily compare the two techniques.

So we will have fruits (Apple, Banana, and Cantaloupe), which will have three properties: Name, Mass, and Color. All fruits will have the same interface: Fruit.

Here is the code for Fruit:

C#
public class Fruit
{
    public virtual float Mass
    {
      get { return (float.NaN); }
    }
    
    public virtual string Color
    {
      get { return (null); }
    }
    
    public virtual string Name
    {
      get { return (string.Empty ); }
    }

Apple, Banana, and Cantaloupe derive from Fruit. Here is, for example, the code for Banana:

C#
public class Banana : Fruit
{
    public override float Mass
    {
        get { return (92.0f); }
    }
    
    public override string Color
    {
        get { return ("Yellow"); }
    }
    
    public override string Name
    {
        get { return ("Banana"); }
    }
}

A collection of fruits will be implemented like this:

C#
public FruitBasket : CollectionBase
{
    /// <sumary>
    /// Get or set the <see cref='Fruit'/> at the specified index
    /// </sumary>
    /// <value></value>
    public virtual Fruit this[int index]
    {
      get { return (Fruit) List[index]; }
      set { List[index] = value; }
    }

    /// <sumary>
    /// Add the specified Fruit  to the collection
    /// </sumary>
    /// <param name='value'>Value.</param>
    /// <returns></returns>;
    public virtual int Add(Fruit value)
    {
        return List.Add(value);
    }

And now, as seen in the Background section, we have to do two steps to implement sorting.

First, create a class which implements the IComparer interface. This comparer will have two properties. One which holds the property name of the object to compare, and the second to define if we want to sort objects ascending or descending. The comparer will also have a Compare method, allowing us to define how to compare objects. Here is the code for the IComparer object:

C#
public enum SortOrderEnum
{
    Ascending,
    Descending
}

public class GenericComparer : IComparer
{
    private String _Property = null;
    private SortOrderEnum _SortOrder = SortOrderEnum.Ascending;
    
    public String SortProperty
    {
        get { return _Property; }
        set { _Property = value; }
    }

    public SortOrderEnum SortOrder
    {
        get { return _SortOrder; }
        set { _SortOrder = value; }
    }

    public int Compare(object x, object y)
    {
        Fruit ing1;
        Fruit ing2;
        
        if (x is Fruit)
            ing1 = (Fruit) x;
        else
            throw new ArgumentException("Object is not of type Fruit");

        if (y is Fruit)
            ing2 = (Fruit) y;
        else
            throw new ArgumentException("Object is not of type Fruit");
        
        if (this.SortOrder.Equals(SortOrderEnum.Ascending))
            return ing1.CompareTo(ing2, this.SortProperty);
        else
            return ing2.CompareTo(ing1, this.SortProperty);
    }
}

As you can see, the comparer object calls the CompareTo method of the Fruit object to compare objects. This is where we will use Reflection to make this method generic. The CompareTo method will use the object and the property passed as arguments to get values to compare, using PropertyInfo. Here is the code of the method:

C#
public int CompareTo(object obj, string Property)
{
    try
    {
        Type type = this.GetType();
        PropertyInfo propertie = type.GetProperty(Property);
        
        
        Type type2 = obj.GetType();
        PropertyInfo propertie2 = type2.GetProperty(Property);
        
        object[] index = null;
        
        object Obj1 = propertie.GetValue(this, index);
        object Obj2 = propertie2.GetValue(obj, index);
        
        IComparable Ic1 = (IComparable) Obj1;
        IComparable Ic2 = (IComparable) Obj2;

        int returnValue = Ic1.CompareTo(Ic2);
        
        return returnValue;
        
    }
    catch (Exception Ex)
    {
        throw new ArgumentException("CompareTo is not possible !");
    }
}

The last thing to do is to implement the Sort method on the collection. This is done by adding this code to the collection:

C#
public void Sort(String SortBy, SortOrderEnum SortOrder)
{
    GenericComparer comparer = new GenericComparer();
    comparer.SortProperty = SortBy;
    comparer.SortOrder = SortOrder;
    this.InnerList.Sort(comparer);
}

You have now a fully working collection that we can sort on all properties, with no need to add code when we add a property.

Using the code:

C#
FruitBasket FB = new FruitBasket() ;

Fruit.Banana banana = new Fruit.Banana() ;
FB.Add(banana);

Fruit.Apple apple = new Fruit.Apple() ;
FB.Add(apple);

Fruit.Cantaloupe cantaloupe = new Fruit.Cantaloupe()  ;
FB.Add(cantaloupe);

Sorting on name:

C#
FB.Sort("Name",SortOrderEnum.Ascending );
foreach(Fruit fruit in FB)
{
    tvwDemo.Nodes.Add( " " + fruit.Name );
}

Conclusion

I haven't compared the time required for sorting on hundreds of objects, but we can assume that the generic IComparer will be slower (thanks to Reflection) than a specific IComparer. But enabling sort on any property without adding any code perhaps is worth this weakness.

History

  • Version 1 - 15 December 05 - Original version.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionHow to Order the Elements of Class Pin
DeltaSoft8-Aug-07 21:37
DeltaSoft8-Aug-07 21:37 
GeneralCaching Pin
Joshua Nussbaum23-Nov-06 17:12
Joshua Nussbaum23-Nov-06 17:12 
QuestionHandling nulls Pin
sgartner7-Aug-06 11:02
sgartner7-Aug-06 11:02 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.