How to Sort objects in collection. IComparer, IComparable
Probably you know about the IComparer and IComparable interfaces which bring us ability to sort items inside of the collection. I would like to talk about them as I know a lot of developers getting confused during implementation sorting functionality.
One dimensional sorting, IComparable.
IComparable interface allow object to be sorted in collection based on some key. By inheriting object from IComparable interface we are making object "comparable" to its type in collection. How it works?
IComparable interface contains one public method CompareTo(object obj) which we have to implement in our class. Let's say we have some class named Employee which contains information about company employee and looks like this :
using System;
namespace SortingObjectsInCollecton
{
public class Employee
{
private int _id;
public int Id
{
get{ return _id;}
}
private string _employeeName;
public string EmployeeName
{
get { return _employeeName; }
}
private DateTime _dateOfHire;
public DateTime DateOfHire
{
get { return _dateOfHire; }
}
public Employee(int id, string name, DateTime dateOfHire)
{
_id = id;
_employeeName = name;
_dateOfHire = dateOfHire;
}
}
}
We have a simple class which contains three properties with different types: integer, string and datetime. Now let's us to make it "comparable" to have ability to sort it after. To achieve that we will inherit our class from IComparable interface.
using System;
namespace SortingObjectsInCollecton
{
public class Employee : IComparable
{
private int _id;
public int Id
{
get{ return _id;}
}
private string _employeeName;
public string EmployeeName
{
get { return _employeeName; }
}
private DateTime _dateOfHire;
public DateTime DateOfHire
{
get { return _dateOfHire; }
}
public Employee(int id, string name, DateTime dateOfHire)
{
_id = id;
_employeeName = name;
_dateOfHire = dateOfHire;
}
/// <summary>
/// Implementation of IComparble interface's compare method.
/// </summary>
/// <param name="obj">Object which should be compared
/// with the current one.</param>
/// <returns>Returns the value which shows position of
/// object compare to current.</returns>
public int CompareTo(object obj)
{
Employee temp = (Employee) obj;
if(this.Id>temp.Id)
return 1;
else if(this.Id<temp.Id)
return -1;
else
return 0;
}
}
}
As you can see, we implemented public CompareTo (object obj) method where input parameter obj will be compared with the current type where returned value represents order compare to passed object, whether it should be positioned before or after. Let's try it. In our Main method of our console application project we will create a collection, fill it by some Employee objects and then sort it.
using System;
namespace SortingObjectsInCollecton
{
class Program
{
static void Main(string[] args)
{
Employee[] employees =new Employee[5];
employees[0] = new Employee(5, "Jhon", new DateTime(2006, 7, 10));
employees[1] = new Employee(2, "David", new DateTime(2006, 11, 23));
employees[2] = new Employee(1, "Alex", new DateTime(2004, 5, 4));
employees[3] = new Employee(10, "Ann", new DateTime(2005, 4, 9));
employees[4] = new Employee(4, "Todd", new DateTime(2003, 2, 18));
Array.Sort(employees);
foreach (Employee employee in employees)
{
Console.WriteLine("Id={0} : Name={1} : DateOfHire={2}",
employee.Id,
employee.EmployeeName,
employee.DateOfHire.ToShortDateString());
}
Console.Read();
}
}
}
As you can see we added 5 Employee objects into the collection and before writing them out we used static Sort() method of Array class and passed there our collection like input parameter. Thanks that our class was inherited from IComparable Sort method run CompareTo method of Employee class with our implementation.
And as you can see the result output have been sorted by Id of Employee even we added object with mixed order. This is called as one dimensional sorting as it is sorting just by Id property and with Ascending order. But what if we want to change the order of sorting or to change the property which should be used for compare. Now it is a time to look into the IComparer interface.
Multidimensional sorting, IComparer.
To achieve the goal we have to implement class with ICompare interface. Usually, ICompare is not implemented on a class which should be sorted but on external helper class. So, it is the time to create our own comparer class:
namespace SortingObjectsInCollecton
{
public class EmployeeComparer : System.Collections.IComparer
{
public int Compare(object x, object y)
{
Employee tempx = (Employee) x;
Employee tempy = (Employee) y;
return tempx.Id.CompareTo(tempy.Id);
}
}
}
As you can see we created EmployeeComparer class inherited from IComparer and what we just need to implement Compare() method brought by interface. By the way ICompare is part of System.Collections namespace. In current implementation compare class will bring us the same result as the previous example with IComparable implementation. How to use it? Basically with the same way:
using System;
namespace SortingObjectsInCollecton
{
class Program
{
static void Main(string[] args)
{
Employee[] employees =new Employee[5];
employees[0] = new Employee(5, "Jhon", new DateTime(2006, 7, 10));
employees[1] = new Employee(2, "David", new DateTime(2006, 11, 23));
employees[2] = new Employee(1, "Alex", new DateTime(2004, 5, 4));
employees[3] = new Employee(10, "Ann", new DateTime(2005, 4, 9));
employees[4] = new Employee(4, "Todd", new DateTime(2003, 2, 18));
Array.Sort(employees, new EmployeeComparer());
foreach (Employee employee in employees)
{
Console.WriteLine("Id={0} : Name={1} : DateOfHire={2}",
employee.Id,
employee.EmployeeName,
employee.DateOfHire.ToShortDateString());
}
Console.Read();
}
}
}In Sort method along with collection parameter we are passing comparer object.
As you can see we have the same result as in the with the IComparable:
But now all our attention should switch to EmployeeComparer implementation. Lets add two public enumerations and two public properties which will hold the enumeration value into the comparer class:
namespace SortingObjectsInCollecton
{
public class EmployeeComparer : System.Collections.IComparer
{
public enum SortDirection
{
/// <summary>
/// Ascending
/// </summary>
Asc,
/// <summary>
/// Descending
/// </summary>
Desc
}
public enum SortingProperty
{
/// <summary>
/// EmployeeId
/// </summary>
id,
/// <summary>
/// Employee name
/// </summary>
name,
/// <summary>
/// Date when employee was hired.
/// </summary>
date
}
private SortDirection _sortOrder = SortDirection.Asc;
public SortDirection SortOrder
{
get { return _sortOrder; }
set { _sortOrder = value;}
}
private SortingProperty _sortBy = SortingProperty.id;
public SortingProperty SortBy
{
get { return _sortBy; }
set { _sortBy = value; }
}
public int Compare(object x, object y)
{
int compareValue = 0;
Employee tempx = (Employee) x;
Employee tempy = (Employee) y;
switch(_sortBy)
{
case SortingProperty.id :
{
if (_sortOrder == SortDirection.Asc)
compareValue = tempx.Id.CompareTo(tempy.Id);
else
compareValue = tempy.Id.CompareTo(tempx.Id);
break;
}
case SortingProperty.name :
{
if (_sortOrder == SortDirection.Asc)
compareValue = tempx.EmployeeName.CompareTo(tempy.EmployeeName);
else
compareValue = tempy.EmployeeName.CompareTo(tempx.EmployeeName);
break;
}
case SortingProperty.date:
{
if (_sortOrder == SortDirection.Asc)
compareValue = tempx.DateOfHire.CompareTo(tempy.DateOfHire);
else
compareValue = tempy.DateOfHire.CompareTo(tempx.DateOfHire);
break;
}
}
return compareValue;
}
}
}
As you see, we have now implemented object compare by all three properties and in both orders (ASC, DESC). Let's now use them:
using System;
namespace SortingObjectsInCollecton
{
class Program
{
static void Main(string[] args)
{
Employee[] employees =new Employee[5];
employees[0] = new Employee(5, "Jhon", new DateTime(2006, 7, 10));
employees[1] = new Employee(2, "David", new DateTime(2006, 11, 23));
employees[2] = new Employee(1, "Alex", new DateTime(2004, 5, 4));
employees[3] = new Employee(10, "Ann", new DateTime(2005, 4, 9));
employees[4] = new Employee(4, "Todd", new DateTime(2003, 2, 18));
EmployeeComparer ec = new EmployeeComparer();
ec.SortBy = EmployeeComparer.SortingProperty.name;
ec.SortOrder = EmployeeComparer.SortDirection.Desc;
Array.Sort(employees, ec);
foreach (Employee employee in employees)
{
Console.WriteLine("Id={0} : Name={1} : DateOfHire={2}",
employee.Id,
employee.EmployeeName,
employee.DateOfHire.ToShortDateString());
}
Console.Read();
}
}
}
Before sorting we created new instance of EmployeeCompare object and set the sorting order and property to appropriate values.
Try to change sorting criteria and you will see the appropriate result on a screen. Well, basically that's it. :) As you see nothing special in it.
Ok, but what about having custom collection based on ArrayList class. Do we need to sort it throw Array.Sort() method ? Let's see. First we will create collection class.
using System.Collections;
namespace SortingObjectsInCollecton
{
public class EmployeeCollection : ArrayList
{
public void Add(Employee employee)
{
base.Add(employee);
}
public void Sort(EmployeeComparer.SortingProperty sp,
EmployeeComparer.SortDirection sd)
{
EmployeeComparer ec = new EmployeeComparer();
ec.SortBy = sp;
ec.SortOrder = sd;
base.Sort(ec);
}
}
}
Now we will replace the code in the Main method to have EmployeeCollection instead of Employee array.
using System;
namespace SortingObjectsInCollecton
{
class Program
{
static void Main(string[] args)
{
EmployeeCollection employees = new EmployeeCollection();
employees.Add(new Employee(5, "Jhon", new DateTime(2006, 7, 10)));
employees.Add(new Employee(2, "David", new DateTime(2006, 11, 23)));
employees.Add(new Employee(1, "Alex", new DateTime(2004, 5, 4)));
employees.Add(new Employee(10, "Ann", new DateTime(2005, 4, 9)));
employees.Add(new Employee(4, "Todd", new DateTime(2003, 2, 18)));
employees.Sort(EmployeeComparer.SortingProperty.name,
EmployeeComparer.SortDirection.Desc);
foreach (Employee employee in employees)
{
Console.WriteLine("Id={0} : Name={1} : DateOfHire={2}",
employee.Id,
employee.EmployeeName,
employee.DateOfHire.ToShortDateString());
}
Console.Read();
}
}
}
And us you can see the result is the same but with different implementation.
Than's it.
Basically that's it. As you can see nothing special. Really easy. I'll try in a future to make some blog about sorting of Generic objects. Hopefully this blog will help you to implement "Sorting" functionality.