Click here to Skip to main content
Click here to Skip to main content

Sorting using IComparer classes (including Generics)

, 23 Jun 2006 CPOL
Rate this:
Please Sign up or sign in to vote.
Methodology demonstration for sorting collections by the item class properties or methods.

Sample Application screen

Introduction

I needed a methodology for sorting collections, by either properties or methods of the collection item class. I also needed to change the sort at runtime, and compare the performance of various IComparer implementations. This demo project is the result of this research.

Using the code

The source code contains this IComparer class implementation plus three others:

Generic Comparer - a class to be used for generic sorting of collection items based on a property or a method.

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

namespace SortingMethods
{
    /// <summary>
    /// Reflection based, Generic Comparer
    /// class for in-line or instance based Sorting
    /// </summary>
    /// <example>
    /// List<DataObject> array = new List<DataObject>();
    /// 
    /// ... Code to populate list ...
    /// 
    /// array.sort(new GenericComparer<DataObject>("ID", "asc", null));
    /// 
    /// or
    /// 
    /// GenericComparer genericComparer = 
    ///       new GenericComparer<DataObject>();
    /// genericComparer.MemberName = "ID";
    /// genericComparer.SortOrder = "asc";
    /// 
    /// array.sort(genericComparer);
    /// </example>
    public class GenericComparer<T> : IComparer<T>
    {
        #region Fields
        private string memberName = string.Empty;
        private string sortOrder = string.Empty;
        private List<object> methodParameters = new List<object>();
        PropertyInfo propertyInfo = null;
        MethodInfo methodInfo = null;
        #endregion

        #region Properties
        public string MemberName
        {
            get
            {
                return memberName;
            }
            set
            {
                memberName = value;
                GetReflected();
            }
        }

        public string SortOrder
        {
            get
            {
                return sortOrder;
            }
            set
            {
                sortOrder = value;
            }
        }

        public List<object> MethodParameters
        {
            get
            {
                return methodParameters;
            }
        }
        #endregion

        #region Ctors
        /// <summary>
        /// Default constructor for use in instances.
        /// </summary>
        public GenericComparer()
        {
        }

        /// <summary>
        /// Constructor for in-line instantiation
        /// </summary>
        /// <param name="memberName">string of the member name to use for comparison
        /// can be either a method name or a property.
        /// </param>
        /// <param name="sortOrder">string of the sort order (case independent). 
        /// "ASC" for ascending order or anyting else for descending order</param>
        /// <param name="methodParameters">object array
        ///      of parameters to use for method, null otherwise.</param>
        public GenericComparer(string memberName, string sortOrder, 
               List<object> methodParameters)
        {
            this.memberName = memberName;
            this.sortOrder = sortOrder;
            this.methodParameters = methodParameters;

            GetReflected();
        }
        #endregion

        #region Private methods
        /// <summary>
        /// Sets the global field, propertyInfo
        /// and/or memberInfo using the underlying Type
        /// </summary>
        private void GetReflected()
        {
            Type[] underlyingTypes = this.GetType().GetGenericArguments();
            Type thisUnderlyingtype = underlyingTypes[0];

            MemberInfo[] mi = thisUnderlyingtype.GetMember(memberName);
            if (mi.Length > 0)
            {
                if (mi[0].MemberType == MemberTypes.Property)
                {
                    propertyInfo = thisUnderlyingtype.GetProperty(memberName);
                }
                else if (mi[0].MemberType == MemberTypes.Method)
                {
                    Type[] signatureTypes = new Type[0];
                    if (methodParameters != null && methodParameters.Count > 0)
                    {
                        signatureTypes = new Type[methodParameters.Count];
                        for (int i = 0; i < methodParameters.Count; i++)
                        {
                            signatureTypes[i] = methodParameters[i].GetType();
                        }
                        methodInfo = 
                          thisUnderlyingtype.GetMethod(memberName, 
                          signatureTypes);
                    }
                    else
                    {
                        methodInfo = 
                          thisUnderlyingtype.GetMethod(memberName, 
                          signatureTypes);
                    }
                }
                else
                {
                    throw new Exception("Member name: " + memberName + 
                          " is not a Public Property or " + 
                          "a Public Method in Type: " + 
                          thisUnderlyingtype.Name + ".");
                }
            }
            else
            {
                throw new Exception("Member name: " + 
                          memberName + " not found.");
            }
        }

        /// <summary>
        /// Return an IComparable for use in the Compare method
        /// </summary>
        /// <param name="obj">object to get IComparable from</param>
        /// <returns>IComparable for this object</returns>
        private IComparable GetComparable(T obj)
        {
            if (methodInfo != null)
            {
                return (IComparable)methodInfo.Invoke(obj, 
                                    methodParameters.ToArray());
            }
            else
            {
                return (IComparable)propertyInfo.GetValue(obj, null);
            }

        }
        #endregion

        #region IComparer Implementation
        /// <summary>
        /// Implementing method for IComparer
        /// </summary>
        /// <param name="objOne">Object to compare from</param>
        /// <param name="objTwo">Object to compare to</param>
        /// <returns>int of the comparison, or 0 if equal</returns>
        public int Compare(T objOne, T objTwo)
        {
            IComparable iComparable1 = GetComparable(objOne);
            IComparable iComparable2 = GetComparable(objTwo);

            if (sortOrder != null && sortOrder.ToUpper().Equals("ASC"))
            {
                return iComparable1.CompareTo(iComparable2);
            }
            else
            {
                return iComparable2.CompareTo(iComparable1);
            }
        }
        #endregion
    }
}

Points of interest

The Generic Comparer and the General Comparer can be re-used in your projects "as is".

Acknowledgment

Thanks to all who have contributed comments! I have recoded the GenericComparer to create the ProgramInfo and/or MethodInfo objects in the constructor or when the MemberName property is set. The GetReflected() method uses Reflection to get the Type of the underlying object to get the PropertyInfo and/or MethodInfo.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Jeff Bramlett
Software Developer (Senior)
United States United States
Website: http://www.somedeveloper.us

Comments and Discussions

 
QuestionHow To compare Structs and Class Pinmemberraj_32720-Mar-07 2:59 
GeneralThis can be tons faster by using LCG. PinmemberMarc Brooks14-Jun-06 12:14 
GeneralRe: This can be tons faster by using LCG. PinmemberDr. Heiko Voß23-Jun-06 9:59 
GeneralRe: This can be tons faster by using LCG. PinmemberMarc Brooks23-Jun-06 12:24 
Not sure what the problem might be, you are welcome to e-mail me directly at IDisposable@gmail.com and I'll send you the current version.
 
http://musingmarc.blogspot.com
GeneralRe: This can be tons faster by using LCG. PinmemberMarc Brooks2-Oct-06 16:21 
GeneralAnother Suggestion PinmemberJames Curran14-Jun-06 8:51 
GeneralAlso [modified] PinmemberBill Pierce14-Jun-06 8:41 
GeneralSuggestion PinmemberSteve Hansen14-Jun-06 6:35 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.141220.1 | Last Updated 23 Jun 2006
Article Copyright 2006 by Jeff Bramlett
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid