CodeBetter.Com
CodeBetter.Com
RSS 2.0 via Feedburner
           Do you Twitter? Follow us @CodeBetter

David Hayden [MVP C#]

         .NET Tutorials, Patterns, and Practices

Implementing IComparable for Sorting Custom Objects

Update 3/6/2005: Check out Implementing IComparer for Sorting Custom Objects

In a couple of other posts

I mentioned the usefulness of overriding Object's virtual methods of Equals and ToString as well as implementing IFormattable for custom formatting.

One of the other useful interfaces you can implement for your custom class is IComparable.  It has one method.

IComparable Defined

Defines a generalized comparison method that a value type or class implements to create a type-specific comparison method.

public int CompareTo(object obj)

I recommend reading the documentation on MSDN for a more thorough analysis:  IComparable Interface, CompareTo

This interface comes into play when you start adding your custom objects into Arrays and ArrayLists.  An ArrayList, for example, has a Sort method that allows you to sort the objects in the ArrayList.  By default, the ArrayList uses the IComparable interface on each of the objects to do the sorting.

In the following example I have taken my person class and implemented IComparable.  At the very end of the Person class you will see the CompareTo method.  I first check to see if the object to be compared is of the same type (Person) and cast is accordingly.  In my implementation I decided to only sort by firstname ascending and thus delegated all the effort of comparison to _firstname, which is of type String and also supports IComparable.

Implementing IComparable
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
    
    public override string ToString()
    {
        return String.Format("{0} {1}, Age = {2}", _firstname,
             _lastname, _age.ToString());
    }

    #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 class TestClass
{
    public static void Main()
    {
        ArrayList people = new ArrayList();
        people.Add(new Person("John","Doe", 84));
        people.Add(new Person("Abby","Normal", 25));
        people.Add(new Person("Jane","Doe", 76));
        
        people.Sort();
        
        foreach (Person p in people)
            Console.WriteLine(p);
            
        Console.ReadLine();
    }
}

When you run the example, which fills a list of Person objects into an ArrayList, called people, it essentially calls people.Sort() which will re-order the objects ascending by firstname.  Very boring, but a hell of a lot better than implementing a sort algorithm yourself.

So, the example is pretty dang limiting as I can't vary the way I want this ArrayList sorted.  Maybe I want to sort the objects by age sometimes or possibly lastname.  This can be done in a hackish sort of way, but you have to make a couple of modifications.

In the example below, I first added an enum, called SortMethod, nested inside the Person Class that would allow me to specify how I want to sort the objects in the ArrayList.  I also added a static property and variable, SortOrder and _sortOrder respectively, that holds the current sort method.  As static members, these operate at the class level and are shared across all objects of type Person (there are drawbacks to doing this, but I won't get into it.). I have also changed the CompareTo method to essentially vary the sorting based on the sort order.

IComparable with Multiple Sorting Options
using System;
using System.Collections;

public class Person : IComparable
{
    public enum SortMethod
        {Firstname = 0,Lastname = 1,Age = 2};
        
    #region Private Members

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

    private static SortMethod _sortOrder;

    #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; }
    }
    
    public static SortMethod SortOrder
    {
        get { return _sortOrder; }
        set { _sortOrder = value; }
    }

    #endregion
    

    #region Contructors

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

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

    #region IComparable Members

    public int CompareTo(object obj)
    {
        if (obj is Person)
        {
            Person p2 = (Person)obj;
            
            switch (_sortOrder)
            {
                case SortMethod.Lastname :
                     return _lastname.CompareTo(p2.Lastname);
                case SortMethod.Age : 
                     return _age.CompareTo(p2.Age);
                case SortMethod.Firstname:
                default:
                    return _firstname.CompareTo(p2.Firstname);
            }
        }
        else
            throw new ArgumentException("Object is not a Person.");
    }

    #endregion
}


public class TestClass
{
    public static void Main()
    {
        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));
        
        people.Sort();
        
        foreach (Person p in people)
            Console.WriteLine(p);
            
        Console.ReadLine();
        
        Person.SortOrder = Person.SortMethod.Lastname;
        
        people.Sort();
        
        foreach (Person p in people)
            Console.WriteLine(p);
            
        Console.ReadLine();
        
        Person.SortOrder = Person.SortMethod.Age;
        
        people.Sort();
        
        foreach (Person p in people)
            Console.WriteLine(p);
            
        Console.ReadLine();

    }
}

When you run the example, it will first sort the people by firstname, which is the default, and then by lastname and finally age.

I am not going to speak about the performance of the QuickSort Algorithm, which is the algorithm used in these cases to sort the objects.  Performance is a very subjective matter that is decided upon on an application-by-application basis.


Published Feb 27 2005, 09:29 AM by David Hayden
Filed under:

Comments

Brendan Tompkins said:

Cool stuff, David!

A while back I posted a CollectionView class that allows you to add sorting to any property of your collection objects.

It's not a perfect solution though, sub obejcts are not handled well, as is multiple propery sorting. Check it out here:

http://codebetter.com/blogs/brendan.tompkins/archive/2004/09/02/24116.aspx
# February 27, 2005 1:57 PM

TrackBack said:

# March 6, 2005 12:32 PM

TrackBack said:

# March 6, 2005 12:39 PM

TrackBack said:

# March 6, 2005 12:42 PM

TrackBack said:

# March 6, 2005 5:48 PM

TrackBack said:

# March 6, 2005 5:49 PM

TrackBack said:

# March 6, 2005 5:53 PM
Check out Devlicio.us!

Our Sponsors

Free Tech Publications

This Blog

Syndication

News

CodeBetter.Com Home