I want some Moore

Blog about stuff and things and stuff. Mostly about SQL server and .Net
posts - 219, comments - 2287, trackbacks - 33

My Links

Advertisement

News

Hi! My name is 
Mladen Prajdić  I'm from Slovenia and I'm currently working as a .Net (C#) and SQL Server developer.

I also speak at local user group meetings and conferences like SQLBits and NT Conference
Welcome to my blog.
SQL Server MVP

My Books

SQL Server MVP Deep Dives 2
The Red Gate Guide to SQL Server Team based Development Free e-book

My Blog Feed via Email
Follow MladenPrajdic on Twitter


Users Online: who's online

Article Categories

Archives

Post Categories

Cool software

Other Blogs

Other stuff

SQL stuff

Sorter class v2 - A generic comparer/sorter class

In my previous post i showed how a generic comparer class in .net C# 2.0 can be made with the use of reflection.

In my constant search to optimize code and make it better i've stumbled upon this article that explaines how IL code generation can be use in this situation. In .net 2.0 emiting IL was made very simple with the indroduction of a DynamicMethod class.

The speed of this approach is comparable to execution time of compiled code.

Result when sorting 10.000  TestClass objects 10 times was 2±0.2 seconds with DynamicMethod and 50±2 seconds with direct use of reflection on my computer. So that's a lot of speed gain :))

The class performs even a bit faster if we know the type of the property we're sorting on but i've chosen to make the class general (property data type unknown).

I didn't include much error handling and sorting on fields which can also be achived with DynamicMethod with FieldInfo class

Sample use:

Random r = new Random();
List<TestClass> testClassList = new List<TestClass>();
for (int i = 0; i < 10000; i++)
{
    testClassList.Add(new TestClass("strType" + r.Next(0, 1000).ToString(), r.Next(15, 50), (decimal)r.NextDouble()));
}
// sorting on properties on their default comparer
Sorter<TestClass> sort = new Sorter<TestClass>(testClassList, "StringType DESC, DecimalType Desc, IntType asc");
List<TestClass> sorted = sort.GetSortedList();

// if we want to sort some properties on their ToString() representation comparer 
// and not on the default comparer
List<ObjectSortProperty> sortProps = new List<ObjectSortProperty>();
sortProps.Add(new ObjectSortProperty("StringType", SortOrderEnum.DESC));
sortProps.Add(new ObjectSortProperty("DecimalType", SortOrderEnum.DESC, SortOnEnum.String));
sortProps.Add(new ObjectSortProperty("IntType"));
Sorter<TestClass> sort2 = new Sorter<TestClass>(testClassList, sortProps);
List<TestClass> sorted2 = sort2.GetSortedList();

This is the code for the Sorter class and for the TestClass:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
namespace GenericSorter
{
    #region Enums
    /// <summary>
    /// Enum that specifies the sort order
    /// </summary>
    public enum SortOrderEnum { ASC = 1, DESC = -1 };
    /// <summary>
    /// Enum that specifies the sort order type
    /// Default = Compare using types default comparer
    /// String = Compare ToString() values
    /// </summary>
    public enum SortOnEnum { Default = 0, String = 1 };
    #endregion
    #region Sorter class
    /// <summary>
    /// Sorter class
    /// </summary>
    /// <typeparam name="TObject">Generic type. Can be any object.</typeparam>
    public class Sorter<TObject>
    {
        #region variables
        private List<TObject> _OriginalList;
        private List<ObjectSortProperty> _ObjectSortOnProperties; 
        #endregion        
        
        #region Constructors
        /// <summary>
        /// Sort collection of objects
        /// </summary>
        /// <param name="itemCollection">Object collection</param>
        /// <param name="orderBy">SQL style Order by. Property names are CASE SENSITIVE. Properties are sorted with property datatype default comparer</param>
        public Sorter(IEnumerable itemCollection, string orderBy)
            : this(itemCollection, ParseOrderBy(orderBy))
        { }        
        
        /// <summary>
        /// Sort collection of objects
        /// </summary>
        /// <param name="itemCollection">Object collection</param>
        /// <param name="objectSortProperties">List of objects' sort properties to sort on</param>
        public Sorter(IEnumerable itemCollection, IEnumerable<ObjectSortProperty> objectSortProperties)
        {
            _OriginalList = new List<TObject>();
            _ObjectSortOnProperties = new List<ObjectSortProperty>();
            SetNewCollection(itemCollection, objectSortProperties);
        }
        #endregion
        #region Static private methods
        private static List<ObjectSortProperty> ParseOrderBy(string orderBy)
        {
            if (orderBy == null || orderBy.Length == 0)
                return new List<ObjectSortProperty>(0);
            string[] props = orderBy.Split(new char[] { ',' } , StringSplitOptions.RemoveEmptyEntries);
            List<ObjectSortProperty> properties = new List<ObjectSortProperty>(props.Length);
            for (int i = 0; i < props.Length; i++)
            {
                string prop = props[i].Trim();
                string[] words = prop.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
                if (words.Length > 2)
                    throw new ArgumentException("Too many words in sort expression", "orderBy");
                if (words.Length == 2 && (!words[1].Equals("DESC", StringComparison.OrdinalIgnoreCase) && !words[1].Equals("ASC", StringComparison.OrdinalIgnoreCase)))
                    throw new ArgumentException("The second word in the sort expression must be ASC or DESC", "orderBy");
                if (words.Length == 1 || words[1].Equals("ASC", StringComparison.OrdinalIgnoreCase))
                    properties.Add(new ObjectSortProperty(words[0], SortOrderEnum.ASC));
                else
                    properties.Add(new ObjectSortProperty(words[0], SortOrderEnum.DESC));
            }
            return properties;
        }
        #endregion
        #region Public methods
        /// <summary>
        /// Gets the sorted list of items
        /// </summary>
        /// <returns>Generic list of sorted objects</returns>
        public List<TObject> GetSortedList()
        {
            return _OriginalList;
        }
        /// <summary>
        /// Gets the sorted array of items
        /// </summary>
        /// <returns>Generic array of sorted objects</returns>
        public TObject[] GetSortedArray()
        {
            return _OriginalList.ToArray();
        }
        /// <summary>
        /// Set new collection to sort on already given properties
        /// </summary>
        /// <param name="newCollection">Object implemeting IEnumerable to sort</param>
        public void SetNewCollection(IEnumerable newCollection)
        {
            AddObjectsToList(newCollection);
        }
        /// <summary>
        /// Set new collection to sort on new properties
        /// </summary>
        /// <param name="newCollection">Object implemeting IEnumerable to sort</param>
        /// <param name="objectSortProperty"></param>
        public void SetNewCollection(IEnumerable newCollection, IEnumerable<ObjectSortProperty> objectSortProperties)
        {
            AddObjectSortPropertiesToList(objectSortProperties);
            AddObjectsToList(newCollection);
        }
        public void SetNewSortProperties(IEnumerable<ObjectSortProperty> objectSortProperties)
        {
            AddObjectSortPropertiesToList(objectSortProperties);
            SortList();
        }
        private void AddObjectsToList(IEnumerable newCollection)
        {
            _OriginalList.Clear();
            foreach (TObject item in newCollection)
                _OriginalList.Add(item);
            SortList();
        }
        private void AddObjectSortPropertiesToList(IEnumerable<ObjectSortProperty> objectSortProperties)
        {
            _ObjectSortOnProperties.Clear();
            foreach (ObjectSortProperty property in objectSortProperties)
                _ObjectSortOnProperties.Add(property);
        }
        #endregion
        #region Private Methods
        private void SortList()
        {        
            // if one it means we're sorting on one property
            if (_ObjectSortOnProperties.Count == 1)
            {
                // sort based on the SortOn property
                if (_ObjectSortOnProperties[0].SortOn == SortOnEnum.String)
                    _OriginalList.Sort(CompareSinglePropertyString);
                else
                    _OriginalList.Sort(CompareSinglePropertyDefault);
            }
            // ... else do complex multi property sort
            else
            {
                _OriginalList.Sort(CompareValuesMultiProperty);
            }
        }
        #region Comparer stuff
        /// <summary>
        /// Compares the current objects properties with another object properties of the same type. 
        /// </summary>
        /// <param name="o1">First value to compare</param>
        /// <param name="o2">Second value to compare</param>
        /// <returns>Returns the IComparable.CompareTo result</returns>
        private int CompareSinglePropertyDefault(TObject o1, TObject o2)
        {            
            PropertyValueDelegate<TObject> GetPropValue = GetPropertyValueDelegate<TObject>(_ObjectSortOnProperties[0].PropertyName);
            object obj1 = GetPropValue(o1);
            object obj2 = GetPropValue(o2);
            // if sort order is decsending then reverse the result.
            return _ObjectSortOnProperties[0].SortOrderMultiplier * ((IComparable)obj1).CompareTo(obj2);
        }
        private int CompareSinglePropertyString(TObject o1, TObject o2)
        {
            PropertyValueDelegate<TObject> GetPropValue = GetPropertyValueDelegate<TObject>(_ObjectSortOnProperties[0].PropertyName);
            string obj1 = GetPropValue(o1).ToString();
            string obj2 = GetPropValue(o2).ToString();
            // if sort order is decsending then reverse the result.
            return _ObjectSortOnProperties[0].SortOrderMultiplier * obj1.CompareTo(obj2);
        }
        private int CompareValuesMultiProperty(TObject o1, TObject o2)
        {
            int sortResult = 0;            
            List<ObjectSortProperty>.Enumerator enmr = _ObjectSortOnProperties.GetEnumerator();
            while (sortResult == 0 && enmr.MoveNext())
            {
                PropertyValueDelegate<TObject> GetPropValue = GetPropertyValueDelegate<TObject>(enmr.Current.PropertyName);
                object obj1 = GetPropValue(o1);
                object obj2 = GetPropValue(o2);
                // reflection Method - Old
                /*object obj1 = typeof(TObject).GetProperty(enmr.Current.PropertyName).GetValue(o1, null);
                object obj2 = typeof(TObject).GetProperty(enmr.Current.PropertyName).GetValue(o2, null);*/
                if (enmr.Current.SortOn == SortOnEnum.String)
                {
                    obj1 = obj1.ToString();
                    obj2 = obj2.ToString();
                }
                // if sort order is descending then reverse the result.
                sortResult = enmr.Current.SortOrderMultiplier * ((IComparable)obj1).CompareTo(obj2);
            }
            enmr.Dispose();
            return sortResult;
        }
        /// <summary>
        /// Compares the current instance with another simple object of the same type. 
        /// </summary>
        /// <param name="o1">First value to compare</param>
        /// <param name="o2">Second value to compare</param>
        /// <returns>Returns the IComparable.CompareTo result</returns>
        private int CompareSimple(TObject o1, TObject o2)
        {
            return _ObjectSortOnProperties[0].SortOrderMultiplier * ((IComparable)o1).CompareTo(o2);
        }
        #endregion
        #endregion
        #region IL generator
        public delegate object PropertyValueDelegate<TObj>(TObj obj);
        public static PropertyValueDelegate<TObj> CreatePropertyValueDelegate<TObj>(string propertyName)
        {
            // Use Reflection to get the MethodInfo for our Property's get accessor
            MethodInfo mi = typeof(TObj).GetMethod("get_" + propertyName);
            if (mi == null)
                throw new Exception("Property '" + propertyName + "' doesn't have a get implementation");
                        
            // Create a new Dynamic Method for retrieving the Property's value
            DynamicMethod dm = new DynamicMethod("Get" + propertyName + "PropertyValue",
                typeof(object), new Type[] { typeof(TObj) }, typeof(TObj).Module);
            // Let's Emit some IL code to actually call the Property's accessor and return the value.
            ILGenerator il = dm.GetILGenerator();
            // Load arg 0 (Object) onto the stack
            il.Emit(OpCodes.Ldarg_0);
            // Call the property's get accessor (method) on the object that was loaded onto the stack
            il.EmitCall(OpCodes.Call, mi, null);
            // if the property's get accessor (method) returns a value(int, datetime, etc) type then we have to box it
            if (mi.ReturnType.IsValueType)
                il.Emit(OpCodes.Box, mi.ReturnType);
            
            // Return the value (back to the caller) that was returned (to us) from calling the method above
            il.Emit(OpCodes.Ret);
            // Turn the Dynamic Method into a delegate and return it
            return (PropertyValueDelegate<TObj>)dm.CreateDelegate(typeof(PropertyValueDelegate<TObj>));
        }
        private static Hashtable StoredDelegatesHashTable = new Hashtable();
        public static PropertyValueDelegate<TObj> GetPropertyValueDelegate<TObj>(string propertyName)
        {            
            object returnValue = StoredDelegatesHashTable[propertyName];
            if (returnValue == null)
            {
                returnValue = CreatePropertyValueDelegate<TObj>(propertyName);
                StoredDelegatesHashTable[propertyName] = returnValue;
            }
            return (PropertyValueDelegate<TObj>)returnValue;
        }
        #endregion
    }
    #endregion
    #region ObjectSortProperty class
    public class ObjectSortProperty
    {
        #region Variables
        private string _PropertyName = string.Empty;
        private SortOnEnum _SortOn = SortOnEnum.Default;
        private int _SortOrder = Convert.ToInt16(SortOrderEnum.ASC);
        #endregion
        #region Constructor
        public ObjectSortProperty(string propertyName)
            : this(propertyName, SortOrderEnum.ASC)
        { }
        public ObjectSortProperty(string propertyName, SortOrderEnum sortOrder)
            : this(propertyName, sortOrder, SortOnEnum.Default)
        { }
        /// <summary>
        /// Constructor for property to sort an object on
        /// </summary>
        /// <param name="propertyName">Object's property to sort on</param>
        /// <param name="sortOrder">Sort order</param>
        /// <param name="sortOn">Sort on object's default comparer or on object.ToString()'s comparer</param>
        public ObjectSortProperty(string propertyName, SortOrderEnum sortOrder, SortOnEnum sortOn)
        {
            _PropertyName = propertyName;
            _SortOn = sortOn;
            this.SortOrder = sortOrder;
        }
        #endregion
        #region Properties
        /// <summary>
        /// Object's Property name to sort on
        /// </summary>
        public string PropertyName
        {
            get { return _PropertyName; }
            set { _PropertyName = value; }
        }
        /// <summary>
        /// Sort on object's default comparer or on object.ToString()'s comparer
        /// </summary>
        public SortOnEnum SortOn
        {
            get { return _SortOn; }
            set { _SortOn = value; }
        }
        /// <summary>
        /// Sort Order of sorted list
        /// </summary>
        public SortOrderEnum SortOrder
        {
            get
            {
                return (SortOrderEnum)_SortOrder;
            }
            set
            {
                _SortOrder = Convert.ToInt16(value);
            }
        }
        /// <summary>
        /// Get's the integer value of the sort order (1 = ASC, -1 = DESC). Used only for easier multiplication of comparer result so that no enum casting is needed
        /// </summary>
        public int SortOrderMultiplier
        {
            get { return _SortOrder; }
        }
        #endregion
    }
    #endregion
    class TestClass
    {
        private string _StringType = string.Empty;        
        private int _IntType = 0;        
        private decimal _DecimalType;
        public TestClass(string stringType, int intType, decimal decimalType)
        {
            this._StringType = stringType;
            this._IntType = intType;
            this._DecimalType = decimalType;
        }
        public string StringType
        {
            get { return _StringType; }
            set { _StringType = value; }
        }
        public int IntType
        {
            get { return _IntType; }
            set { _IntType = value; }
        }
        public decimal DecimalType
        {
            get { return _DecimalType; }
            set { _DecimalType = value; }
        }
    }
}

Print | posted on Wednesday, July 05, 2006 5:11 PM |

Feedback

Gravatar

# re: Sorter class v2 - A generic comparer/sorter class

How can we use this class with ObjectDataSource. When I try it I receive this error:
The data source 'ObjectDataSource1' does not support sorting with IEnumerable data. Automatic sorting is only supported with DataView, DataTable, and DataSet.

5/15/2007 10:38 PM | ugur
Comments have been closed on this topic.

Powered by:
Powered By Subtext Powered By ASP.NET