Introduction
A great article by Ryan Beesley on this site demonstrates how to sort a collection of objects using multiple IComparer
classes. This is a common way of sorting data, and you will find several articles on this subject. I will show you how to sort a collection on multiple properties, using a generic IComparer
class.
Background
Basically, implementing sort on a collection requires two actions:
- Creating a class which implements the
IComparer
interface, and manage, in the Compare
method, the way objects has to be compared.
- Overloading the
Sort
method on the collection, and using the IComparer
class defined on step 1.
The problem with this approach is that you have to create as much classes than there are properties to compare. Using Reflection allows you to create only one comparer class which will manage all possibilities!
Using the code
First, we will create a collection, and the multiple objects that this collection will hold. I've used the same architecture that Ryan Beesley used in his article, so you can easily compare the two techniques.
So we will have fruits (Apple, Banana, and Cantaloupe), which will have three properties: Name
, Mass
, and Color
. All fruits will have the same interface: Fruit
.
Here is the code for Fruit
:
public class Fruit
{
public virtual float Mass
{
get { return (float.NaN); }
}
public virtual string Color
{
get { return (null); }
}
public virtual string Name
{
get { return (string.Empty ); }
}
Apple
, Banana
, and Cantaloupe
derive from Fruit
. Here is, for example, the code for Banana
:
public class Banana : Fruit
{
public override float Mass
{
get { return (92.0f); }
}
public override string Color
{
get { return ("Yellow"); }
}
public override string Name
{
get { return ("Banana"); }
}
}
A collection of fruits will be implemented like this:
public FruitBasket : CollectionBase
{
public virtual Fruit this[int index]
{
get { return (Fruit) List[index]; }
set { List[index] = value; }
}
public virtual int Add(Fruit value)
{
return List.Add(value);
}
And now, as seen in the Background section, we have to do two steps to implement sorting.
First, create a class which implements the IComparer
interface. This comparer will have two properties. One which holds the property name of the object to compare, and the second to define if we want to sort objects ascending or descending. The comparer will also have a Compare method, allowing us to define how to compare objects. Here is the code for the IComparer
object:
public enum SortOrderEnum
{
Ascending,
Descending
}
public class GenericComparer : IComparer
{
private String _Property = null;
private SortOrderEnum _SortOrder = SortOrderEnum.Ascending;
public String SortProperty
{
get { return _Property; }
set { _Property = value; }
}
public SortOrderEnum SortOrder
{
get { return _SortOrder; }
set { _SortOrder = value; }
}
public int Compare(object x, object y)
{
Fruit ing1;
Fruit ing2;
if (x is Fruit)
ing1 = (Fruit) x;
else
throw new ArgumentException("Object is not of type Fruit");
if (y is Fruit)
ing2 = (Fruit) y;
else
throw new ArgumentException("Object is not of type Fruit");
if (this.SortOrder.Equals(SortOrderEnum.Ascending))
return ing1.CompareTo(ing2, this.SortProperty);
else
return ing2.CompareTo(ing1, this.SortProperty);
}
}
As you can see, the comparer object calls the CompareTo
method of the Fruit
object to compare objects. This is where we will use Reflection to make this method generic. The CompareTo
method will use the object and the property passed as arguments to get values to compare, using PropertyInfo
. Here is the code of the method:
public int CompareTo(object obj, string Property)
{
try
{
Type type = this.GetType();
PropertyInfo propertie = type.GetProperty(Property);
Type type2 = obj.GetType();
PropertyInfo propertie2 = type2.GetProperty(Property);
object[] index = null;
object Obj1 = propertie.GetValue(this, index);
object Obj2 = propertie2.GetValue(obj, index);
IComparable Ic1 = (IComparable) Obj1;
IComparable Ic2 = (IComparable) Obj2;
int returnValue = Ic1.CompareTo(Ic2);
return returnValue;
}
catch (Exception Ex)
{
throw new ArgumentException("CompareTo is not possible !");
}
}
The last thing to do is to implement the Sort
method on the collection. This is done by adding this code to the collection:
public void Sort(String SortBy, SortOrderEnum SortOrder)
{
GenericComparer comparer = new GenericComparer();
comparer.SortProperty = SortBy;
comparer.SortOrder = SortOrder;
this.InnerList.Sort(comparer);
}
You have now a fully working collection that we can sort on all properties, with no need to add code when we add a property.
Using the code:
FruitBasket FB = new FruitBasket() ;
Fruit.Banana banana = new Fruit.Banana() ;
FB.Add(banana);
Fruit.Apple apple = new Fruit.Apple() ;
FB.Add(apple);
Fruit.Cantaloupe cantaloupe = new Fruit.Cantaloupe() ;
FB.Add(cantaloupe);
Sorting on name:
FB.Sort("Name",SortOrderEnum.Ascending );
foreach(Fruit fruit in FB)
{
tvwDemo.Nodes.Add( " " + fruit.Name );
}
Conclusion
I haven't compared the time required for sorting on hundreds of objects, but we can assume that the generic IComparer
will be slower (thanks to Reflection) than a specific IComparer
. But enabling sort on any property without adding any code perhaps is worth this weakness.
History
- Version 1 - 15 December 05 - Original version.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.