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.

This entry was posted in C#. Bookmark the permalink. Follow any comments here with the RSS feed for this post.

One Response to Implementing IComparable for Sorting Custom Objects

  1. 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

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>