# Monday, March 19, 2007
« DasBlog Insert Code Snippet and Tab Form... | Main | ASP.NET control naming madness »
For no real good reason I decided to implement another reusable IComparer, this time using .NET 2.0 and generics.  My focus this time was on:
  • Better API using generics.
  • SQL compliant ordering clause.
  • Multi-property sort ordering.

GenericComparer<Address> comparer = new GenericComparer<Address>()
   .OrderBy("FirstName, LastName DESC");

As you might guess this IComparer will sort a collection of Address objects by FirstName and then by LastName descending.  The code could still use some cleaning up and I plan on replacing all of the reflection calls using lightweight code generation, but nonetheless it does pass my current set of tests.

public class GenericComparer<T> : IComparer<T>
{
    Type declaringClassType = typeof(T);
    IList<PropertyOrderBy> properties = new List<PropertyOrderBy>();

    /// <summary>
    /// Default ctor instantiates a new GenericComparer instance.
    /// </summary>
    public GenericComparer() { }

    public GenericComparer<T> OrderBy(string sqlOrderBy)
    {
        string[] parts = sqlOrderBy.Split(',');
        foreach (string part in parts)
        {
            string[] orderbyParts = part.Trim().Split(' ');
            string fieldName = orderbyParts[0].Trim();
            string direction = string.Empty;

            if (orderbyParts.Length > 1)
                direction = orderbyParts[1].Trim();

            properties.Add(new PropertyOrderBy(fieldName, direction));
        }

        return this;
    }

    private IComparable GetPropertyValue(object instance, string propertyName)
    {
        // This won't work if the property is overloaded by type
        PropertyInfo info = declaringClassType.GetProperty(propertyName);
        object val = info.GetValue(instance, null);

        IComparable retVal = val as IComparable;
        if (retVal == null)
        {
            throw new ApplicationException(
             propertyName +
             " Type must implement IComparable to be able to use a PropertyComparer.");
        }

        return retVal;
    }

    #region IComparer<T> Members

    public int Compare(T lhsObj, T rhsObj)
    {
        int result = 0;

        foreach (PropertyOrderBy orderBy in properties)
        {
            IComparable lhs = GetPropertyValue(lhsObj, orderBy.PropertyName);
            IComparable rhs = GetPropertyValue(rhsObj, orderBy.PropertyName);

            if (orderBy.SortDirection == "DESC")
                result = rhs.CompareTo(lhs);
            else
                result = lhs.CompareTo(rhs);

            if (result != 0)
                return result;
        }

        return result;
    }

    #endregion    

    private class PropertyOrderBy
    {
        public string PropertyName;
        public string SortDirection = "ASC";

        public PropertyOrderBy(string propertyName, string direction)
        {
            direction = direction.ToUpper();
            if (direction != "ASC" && direction != "DESC" && direction != string.Empty)
                throw new ArgumentOutOfRangeException("direction", "Unknown order by direction: " + direction + ", expected either ASC or DESC.");

            if (direction != string.Empty)
                this.SortDirection = direction;

            this.PropertyName = propertyName;
        }
    }
}

Comments are closed.