Customized Sort Methods for Lists






Few are the programs that don’t use any kinds of lists. Some lists use the key-value thinking to keep order in the line. Others, like stack and queue, uses insert order as sort order. Some lists though don’t care about the order at all, until you give them a reason to by calling their sort() method. But to be able to sort a list, that don’t have a predetermined sort pattern, you either have let every inserted object implement the IComparable interface or create a sort method in a specific sort class that implement the IComparer interface. We’re gonna take a look at the latter solution.

For all the examples in this post the class below is used as data object.

public class SalesReport  
{
    public string UnitName { get; set; }
    public double Amount { get; set; }

    public SalesReport(string _UnitName, double _Amount)
    {
        this.UnitName = _UnitName;
        this.Amount = _Amount;
    }
    public override string ToString()
    {
        return UnitName + ": " + Amount.ToString(); 
    }
}

Since this class doesn’t implement the IComparable interface we need to create a comparer class. In the next code snippet you can see that the Compare method expects two variables of the object type. If objects of other types than SalesReport is sorted then this method will throw an InvalidCastException.

public class SalesReportSort : IComparer
{
    public int Compare(object x, object y)
    {
        SalesReport sr1 = (SalesReport)x;
        SalesReport sr2 = (SalesReport)y;
        return sr1.UnitName.CompareTo(sr2.UnitName);
    }
}

Putting it all together could look something like this. Add the items and then sort the list. Since we only add SalesReport objects to the list the sorting should be just fine.

ArrayList al = new ArrayList();
al.Add(new SalesReport("A", 45.3));
al.Add(new SalesReport("F", 13.5));
al.Add(new SalesReport("C", 41.9));
al.Add(new SalesReport("T", 28.0));
al.Add(new SalesReport("B", 104.6));
al.Sort(new SalesReportSort());
foreach (object o in al)
{
    SalesReport sr = (SalesReport)o;
    Console.WriteLine(sr.ToString());
}
// Wait for a key stroke before closing program
Console.ReadKey();






Using generic lists
One way to make the code more secure is to use a generic list insted of a non-generic one as in the example above. We can then force the SalesReport class to be the only valid object type in the list and therefore eliminate the possibility of casting errors.

When using a generic list we also have to change the sorting classes a bit. To take the example a bit further we will create two different sorting methods this time – one to sort by name and one to sort by amount. As seen in the code snippet below creating a generic sorting class also gets a generic Compare method only accepting the SalesReport class as argument.

public class SalesReport_SortByName : IComparer<SalesReport>
{
    public int Compare(SalesReport x, SalesReport y)
    {
        return x.UnitName.CompareTo(y.UnitName);
    }
}
public class SalesReport_SortByAmount : IComparer<SalesReport>
{
    public int Compare(SalesReport x, SalesReport y)
    {
        return x.Amount.CompareTo(y.Amount);
    }
}

In the last code snippet an example of how to use the generic list and different sort methods is given.

List<SalesReport> sr_list = new List<SalesReport>();
sr_list.Add(new SalesReport("A", 45.3));
sr_list.Add(new SalesReport("F", 13.5));
sr_list.Add(new SalesReport("C", 41.9));
sr_list.Add(new SalesReport("T", 28.0));
sr_list.Add(new SalesReport("B", 104.6));
sr_list.Sort(new SalesReport_SortByName()); 
foreach (SalesReport sr in sr_list)
    Console.WriteLine(sr.ToString());
Console.ReadKey();

sr_list.Sort(new SalesReport_SortByAmount()); 
foreach (SalesReport sr in sr_list)
    Console.WriteLine(sr.ToString());
Console.ReadKey();

It should be obvious that the generic options is more reliable and therefore preferable when the option is given.