David Hayden [MVP C#]

Sponsors

The Lounge

News

  • CodeBetter.Com Home

Other Links

Teas

Patterns & Practices

Florida .NET Developer

Book Reviews

Tampa ASP.NET MVC Developer Group

Advertisement

Images in this post missing? We recently lost them in a site migration. We're working to restore these as you read this. Should you need an image in an emergency, please contact us at imagehelp@codebetter.com
Implementing IComparer for Sorting Custom Objects

In a previous post I discussed the usefulness of implementing IComparable for your classes to assist in sorting your custom objects in ArrayLists.  When you call Sort() on the ArrayList, the default implementation of IComparer is called which uses QuickSort.  QuickSort calls the IComparable implementation of CompareTo() on each of your objects in the ArrayList.

Implementing IComparable for Sorting Custom Objects

Sometime this isn't enough and you will want to pass in your own IComparer for more flexibility, such as to allow you to specify by which field you want to sort the custom objects in the ArrayList.

 

IComparer Defined

Compares two objects and returns a value indicating whether one is less than, equal to or greater than the other.

int Compare(object x, object y);

 

Rather than calling the Sort() method on the ArrayList and using the default IComparer, we are going to use an overloaded method of the ArrayList Sort method which allows us to pass in our own IComparer:

 

Sort(IComparer comparer)

public virtual void Sort(IComparer comparer);

 

In keeping with the same People ArrayList and Person Class we have been using in our examples, here is our custom PersonComparer Class that implements IComparer for use in our custom sorting:

 

PersonComparer
public class PersonComparer : IComparer
{
    public enum ComparisonType
    { Firstname = 1, Lastname = 2, Age = 3 }

    private ComparisonType _comparisonType;

    public ComparisonType ComparisonMethod
    {
        get { return _comparisonType; }
        set { _comparisonType = value; }
    }

    #region IComparer Members

    public int Compare(object x, object y)
    {
        Person p1;
        Person p2;

        if (x is Person)
            p1 = x as Person;
        else
            throw new ArgumentException("Object is not of type Person.");

        if (y is Person)
            p2 = y as Person;
        else
            throw new ArgumentException("Object is not of type Person.");

        return p1.CompareTo(p2, _comparisonType);
    }

    #endregion
}

 

The class itself won't be doing any comparisons.  It only keeps track of which field you want to compare by and delegates the comparison to the Person class itself by calling an overloaded method of CompareTo implemented by Person:

 

Person's Overloaded CompareTo Method
public int CompareTo(Person p2, PersonComparer.ComparisonType comparisonMethod)
{
    switch (comparisonMethod)
    {
        case PersonComparer.ComparisonType.Lastname :
            return _lastname.CompareTo(p2._lastname);
        case PersonComparer.ComparisonType.Age :
            return _age.CompareTo(p2._age);
        case PersonComparer.ComparisonType.Firstname :
        default:
            return _firstname.CompareTo(p2._firstname);
    }
}

 

And, of course, as shown above, the overloaded version of CompareTo just delegates the comparison right back to the String and Int32 classes, which implement IComparable.

Here is the complete code listing that you can run in Snippet Compiler.  It essentially sorts the People ArrayList 3 different times by Lastname, Age, and Firstname, respectively.

 

Testing IComparer
using System;
using System.Collections;

public class Person : IComparable
{        
    #region Private Members

    private string _firstname;
    private string _lastname;
    private int _age;

    #endregion

    #region Properties

    public string Firstname
    {
        get { return _firstname; }
        set { _firstname = value; }
    }

    public string Lastname
    {
        get { return _lastname; }
        set { _lastname = value; }
    }

    public int Age
    {
        get { return _age; }
        set { _age = value; }
    }

    #endregion

    #region Contructors

    public Person (string firstname, string lastname, int age)
    {
        _firstname = firstname;
        _lastname = lastname;
        _age = age;
    }

    #endregion

    #region ToString()
    
    public override string ToString()
    {
        return String.Format("{0} {1}, Age = {2}", _firstname,
            _lastname, _age.ToString());
    }

    #endregion

    #region IComparable Members

    public int CompareTo(object obj)
    {
        if (obj is Person)
        {
            Person p2 = (Person)obj;
            
            return _firstname.CompareTo(p2._firstname);
        }
        else
            throw new ArgumentException("Object is not a Person.");
    }

    #endregion

    public int CompareTo(Person p2, PersonComparer.ComparisonType comparisonMethod)
    {
        switch (comparisonMethod)
        {
            case PersonComparer.ComparisonType.Lastname :
                return _lastname.CompareTo(p2._lastname);
            case PersonComparer.ComparisonType.Age :
                return _age.CompareTo(p2._age);
            case PersonComparer.ComparisonType.Firstname :
            default:
                return _firstname.CompareTo(p2._firstname);
        }
    }
    
    #region PersonComparer

    public class PersonComparer : IComparer
    {
        public enum ComparisonType
        { Firstname = 1, Lastname = 2, Age = 3 }

        private ComparisonType _comparisonType;

        public ComparisonType ComparisonMethod
        {
            get { return _comparisonType; }
            set { _comparisonType = value; }
        }

        #region IComparer Members

        public int Compare(object x, object y)
        {
            Person p1;
            Person p2;

            if (x is Person)
                p1 = x as Person;
            else
                throw new ArgumentException("Object is not of type Person.");

            if (y is Person)
                p2 = y as Person;
            else
                throw new ArgumentException("Object is not of type Person.");

            return p1.CompareTo(p2, _comparisonType);
        }

        #endregion
    }
    
    #endregion
}


public class TestClass
{
    public static void Main()
    {
        // Create ArrayList
        ArrayList people = new ArrayList();
        people.Add(new Person("John","Doe", 76));
        people.Add(new Person("Abby","Normal", 25));
        people.Add(new Person("Jane","Doe", 84));
        
        // Create Person Comparer Class
        Person.PersonComparer comparer = new Person.PersonComparer();
        
        // Sort By Lastname
        comparer.ComparisonMethod = Person.PersonComparer.ComparisonType.Lastname;
        people.Sort(comparer);
        TestClass.DisplayPeople(people);

        // Sort By Age
        comparer.ComparisonMethod = Person.PersonComparer.ComparisonType.Age;
        people.Sort(comparer);
        TestClass.DisplayPeople(people);
        
        // Sort By Firstname
        comparer.ComparisonMethod = Person.PersonComparer.ComparisonType.Firstname;
        people.Sort(comparer);
        TestClass.DisplayPeople(people);
    }
    
    public static void DisplayPeople(ArrayList people)
    {
        foreach (Person p in people)
            Console.WriteLine(p);
            
        Console.ReadLine();
    }
}

 

Now we have a way to sort our custom objects in multiple ways using the custom PersonComparer class.  Check out all my Back to Basics Articles.


Posted 03-06-2005 12:42 PM by David Hayden
Filed under:

[Advertisement]

Comments

Joel Ross wrote re: Implementing IComparer for Sorting Custom Objects
on 03-06-2005 4:23 PM
Thought you might be interested to see this:

http://www.rosscode.com/blog/index.php?title=generic_multiple_sorting_for_typed_colle&more=1&c=1&tb=1&pb=1

This is a generic way to sort on any column of any object using reflection.
TrackBack wrote Implementing IComparer for Sorting Custom Objects
on 03-06-2005 5:48 PM
TrackBack wrote Implementing IComparer for Sorting Custom Objects
on 03-06-2005 5:49 PM
TrackBack wrote Implementing IComparer for Sorting Custom Objects
on 03-06-2005 5:53 PM
TrackBack wrote Implementing IComparable for Sorting Custom Objects
on 03-06-2005 6:08 PM
David Hayden - Sarasota Web Design Development - Florida wrote Strategy Design Pattern and Dependency-Inversion Principle
on 06-27-2005 4:54 PM
Bilal Haidar [MVP] :: Blog wrote Sort List<T> ASC/DESC with Specified Field
on 05-31-2006 3:49 AM
In an ASP.NET 2.0 Web application I am working on, there is a need to use a List<LocalDocument>,...