alkampfer on July 2nd, 2007

Today I was speaking with a colleague about creating a generic IComparer<T> that is able to compare two object based on a property discovered through reflection. Such object will be very useful to sort or find object inside collection of objects when you work with a domain model. In a domain we usually have a lot of objects such as customer, order, and so on, and it happens to have an homogeneous collection of objects in memory that needs to be sorted. Implementing an IComparer object for each combination of property and type is really boring so it useful to create a class capable to compare object based on reflection. A first implementation of the IComparer<T>::Compare() method could be the following

public int Compare(T x, T y) {    
     PropertyInfo propertyInfo = 
typeof(T).GetProperty(SortColumn);
     
IComparable obj1 = (IComparable)propertyInfo.GetValue(x, null);
     
IComparable obj2 = (IComparable)propertyInfo.GetValue(y, null);
     
return (obj1.CompareTo(obj2));
}

This implementation works as expected, but it has bad performance, this because it use reflection to find propertyInfo at each calls of the Compare() method. Since we are dealing with homogeneous collections I know in advance that the collection contains objects of a same type, so a better strategy is to cache the MethodInfo related to the GetterPart of the property into the constructor of the comparer object.

   public class GenericIComparer<T> : IComparer<T> {
 
      
private readonly MethodBase methodInfo;
 
      
internal GenericIComparer(MethodBase methodInfo, Boolean reversed) {
         
this.methodInfo = methodInfo;
         mReversed = reversed;
      }
 
      
public GenericIComparer(String propname)
         : 
this(propname, false) {
      }
 
      
public GenericIComparer(String propname, bool mReversed) :
         
this(typeof(T).GetProperty(propname).GetGetMethod(), mReversed) { }
 
      
public bool Reversed {
         
get { return mReversed; }
         
set { mReversed = value; }
      }
      
private Boolean mReversed = false;
 
      #region IComparer<T> Members
 
      
public int Compare(T x, T y) {
         
IComparable obj1 = (IComparable)methodInfo.Invoke(x, null);
         
IComparable obj2 = (IComparable)methodInfo.Invoke(y, null);
 
         
Int32 result = (obj1.CompareTo(obj2));
         
return mReversed ? -result : result;
      }
 
      #endregion
   }

This version support a property called Reversed that is used to compare in reverse order. As you can see this class grab the methodInfo related to the getter part of the property in the constructor, to reduce calls to GetProperty() function. As you can see this class has an internal constructor that accepts a MethodBase passed from the caller, this is necessary to go a step further into the optimization, a factory class that optimize the creation of GenericIComparer objects.

   public static class GenericComparerFactory {
 
      
private readonly static Dictionary<TypeDictionary<StringRuntimeMethodHandle>> comparers =
         
new Dictionary<TypeDictionary<stringRuntimeMethodHandle>>();
 
      
public static GenericIComparer<T> GetComparer<T>(string propertyName, bool reversed) {
         
//Check if the type array for this comparer was created.
         
if (!comparers.ContainsKey(typeof(T)))
            comparers.Add(
typeof(T), new Dictionary<stringRuntimeMethodHandle>());
         
if (!comparers[typeof(T)].ContainsKey(propertyName))
            comparers[
typeof(T)].Add(
               propertyName, 
               
typeof(T).GetProperty(propertyName).GetGetMethod().MethodHandle);
         
return (GenericIComparer<T>) new GenericIComparer<T>( 
            
MethodInfo.GetMethodFromHandle(comparers[typeof(T)][propertyName]), reversed);
      }
 
      
public static GenericIComparer<T> GetComparer<T>(string propertyName) {
         
return GetComparer<T>(propertyName);
 
      }
   }

This class hold a dictionary of propertyName, RuntimeMethodHandle objects for each type. The RuntimeMethodHandle is a further optimization to save memory used to store MethodInfo objects. A MethodInfo object is a big object to store into memory, so it has a readonly property called MethodHandle that will return a IntPtr that uniquely identify that method info. The GenericComparerFactory can reduce the use of the memory, storing into the dictionary a IntPtr object, and recreating the MethodInfo object with the GetMethodFromHandle() function of the MethodInfo object only when it is necessary. With these optimization the GenericIComparer has a small memory footprint and maximum performance.

Alk.

 



kick it on DotNetKicks.com

3 Responses to “A generic IComparer that works with reflection”

  1. Sweet! Thanks for this code .. it’s nice and simple and does exactly what I need it to do!

    One little error:
    public static GenericIComparer GetComparer(string propertyName) {
    return GetComparer(propertyName);

    should be something like
    public static GenericIComparer GetComparer(string propertyName) {
    return GetComparer(propertyName, false);

    otherwise you have an infinite loop.

  2. Thanks very much for finding the error, That was my fault because when I did the test I did not check 100% code coverage of the test :D.

    Alk.

Trackbacks/Pingbacks

  1. Alkampfer’s Place » Blog Archives » BindingList.Find and NotImplementedException

Leave a Reply