Click here to Skip to main content
15,861,125 members
Articles / Web Development / ASP.NET
Article

Generic List Sort Function

Rate me:
Please Sign up or sign in to vote.
4.41/5 (13 votes)
16 Jul 2008CPOL1 min read 75.6K   591   37   12
This allows the programmer to sort a list based on multiple properties

Introduction

In this article, I will talk about how to sort the generic .NET list using reflection and extension methods in C#. This extension method for the generic list is able to sort a list on multiple properties. The usage is simply text-based, for example:

C#
userlist.Sort("Firstname asc, Birthday desc"); 

Background

Extension methods enable you to "add" methods to existing types without creating a new derived type, recompiling, or otherwise modifying the original type. Extension methods are a special kind of static method, but they are called as if they were instance methods on the extended type. For client code written in C# and Visual Basic, there is no apparent difference between calling an extension method and the methods that are actually defined in a type.

The Code

The following code is the extension method itself, the sort function.

It creates a list of generic comparers (because we want to enable sorting on multiple properties).
Then it loops through the sortExpressions and creates new Comparers.

C#
/// <summary>
/// Sorts the specified list.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="list">The list to be sorted.</param>
/// <param name="sortExpression">The sort expression; format:
/// @param1 [sortdirection], @param2 [sortdirection], @param3 [sortdirection].
/// Valid sortDirections are: asc, desc, ascending and descending.</param>
public static void Sort<T>(this List<T> list, string sortExpression)
{
    string[] sortExpressions = sortExpression.Split(new string[] { "," },
                StringSplitOptions.RemoveEmptyEntries);

    List<GenericComparer> comparers = new List<GenericComparer>();

    foreach (string sortExpress in sortExpressions)
    {
        string sortProperty = sortExpress.Trim().Split(' ')[0].Trim();
        string sortDirection = sortExpress.Trim().Split(' ')[1].Trim();

        Type type = typeof(T);
        PropertyInfo PropertyInfo = type.GetProperty(sortProperty);
        if (PropertyInfo == null)
        {
            PropertyInfo[] props = type.GetProperties();
            foreach (PropertyInfo info in props)
            {
                if (info.Name.ToString().ToLower() == sortProperty.ToLower())
                {
                    PropertyInfo = info;
                    break;
                }
            }
            if (PropertyInfo == null)
            {
                throw new Exception(String.Format("{0} is not a valid property of type:
                    \"{1}\"", sortProperty, type.Name));
            }
        }

        SortDirection SortDirection = SortDirection.Ascending;
        if (sortDirection.ToLower() == "asc" || sortDirection.ToLower() == "ascending")
        {
            SortDirection = SortDirection.Ascending;
        }
        else if (sortDirection.ToLower() == "desc" ||
                sortDirection.ToLower() == "descending")
        {
            SortDirection = SortDirection.Descending;
        }
        else
        {
            throw new Exception("Valid SortDirections are:
                    asc, ascending, desc and descending");
        }

        comparers.Add(new GenericComparer { SortDirection = SortDirection,
                PropertyInfo = PropertyInfo, comparers = comparers });
    }
    listSort(comparers[0].Compare);
}  

The following code shows the GenericComparer class which compares the two elements. If the elements are equal, it will ask the second comparer to compare the second property and so on. This part is recursive!

C#
public class GenericComparer
{
    public List<GenericComparer> comparers { get; set; }
    int level = 0;

    public SortDirection SortDirection { get; set; }
    public PropertyInfo PropertyInfo { get; set; }

    public int Compare<T>(T t1, T t2)
    {
        int ret = 0;

        if (level >= comparers.Count)
            return 0;

        object t1Value = comparers[level].PropertyInfo.GetValue(t1, null);
        object t2Value = comparers[level].PropertyInfo.GetValue(t2, null);

        if (t1 == null || t1Value == null)
        {
            if (t2 == null || t2Value == null)
            {
                ret = 0;
            }
            else
            {
                ret = -1;
            }
        }
        else
        {
            if (t2 == null || t2Value == null)
            {
                ret = 1;
            }
            else
            {
                ret = ((IComparable)t1Value).CompareTo(((IComparable)t2Value));
            }
        }
        if (ret == 0)
        {
            level += 1;
            ret = Compare(t1, t2);
            level -= 1;
        }
        else
        {
            if (comparers[level].SortDirection == SortDirection.Descending)
            {
                ret *= -1;
            }
        }
        return ret;
    }
}

Using the Code

The following example shows us how to use it and the results:

C#
public class ExampleUser
{
    public DateTime Birthday { get; set; }
    public string Firstname { get; set; }
}

public class ExampleClass
{
    public static void Example()
    {
        List<ExampleUser> userlist = new List<ExampleUser>();
        userlist.Add(new ExampleUser
            { Birthday = new DateTime(1988, 10, 1), Firstname = "Bryan" });
        userlist.Add(new ExampleUser
            { Birthday = new DateTime(1986, 11, 4), Firstname = "Michael" });
        userlist.Add(new ExampleUser
            { Birthday = new DateTime(1977, 2, 2), Firstname = "Arjan" });
        userlist.Add(new ExampleUser
            { Birthday = new DateTime(1990, 6, 13), Firstname = "Pieter" });
        userlist.Add(new ExampleUser
            { Birthday = new DateTime(1988, 10, 1), Firstname = "Ruben" });
        userlist.Add(new ExampleUser
            { Birthday = new DateTime(1987, 8, 21), Firstname = "Bastiaan" });
        userlist.Add(new ExampleUser
            { Birthday = new DateTime(1987, 8, 21), Firstname = "Pieter" });

        string unsorted = "Unsorted: ";
        foreach (ExampleUser user in userlist)
        {
            unsorted += String.Format("{0} / {1} {2}", user.Birthday.ToString
                ("dd-MM-yyyy"), user.Firstname, Environment.NewLine);
        }

        userlist.Sort("Firstname asc");
        string sorted1 = "Sorted by Firstname asc: " + Environment.NewLine;
        foreach (ExampleUser user in userlist)
        {
            sorted1 += String.Format("{0} / {1} {2}", user.Birthday.ToString
                ("dd-MM-yyyy"), user.Firstname, Environment.NewLine);
        }

        userlist.Sort("Firstname asc, Birthday desc");
        string sorted2 = "Sorted by Firstname asc, Birtday desc: " + Environment.NewLine;
        foreach (ExampleUser user in userlist)
        {
            sorted2 += String.Format("{0} / {1} {2}", user.Birthday.ToString
                ("dd-MM-yyyy"), user.Firstname, Environment.NewLine);
        }

        userlist.Sort("Birthday asc, Firstname asc");
        string sorted3 = "Sorted by Birthday ascending,
                Firstname asc: " + Environment.NewLine;
        foreach (ExampleUser user in userlist)
        {
            sorted3 += String.Format("{0} / {1} {2}", user.Birthday.ToString
                ("dd-MM-yyyy"), user.Firstname, Environment.NewLine);
        }
    }
}

Results

Unsorted:
01-10-1988 / Bryan
04-11-1986 / Michael
02-02-1977 / Arjan
13-06-1990 / Pieter
01-10-1988 / Ruben
21-08-1987 / Bastiaan
21-08-1987 / Pieter

Sorted by Firstname ascending:
02-02-1977 / Arjan
21-08-1987 / Bastiaan
01-10-1988 / Bryan
04-11-1986 / Michael
21-08-1987 / Pieter
13-06-1990 / Pieter
01-10-1988 / Ruben

Sorted by Firstname ascending, Birtday descending:
02-02-1977 / Arjan
21-08-1987 / Bastiaan
01-10-1988 / Bryan
04-11-1986 / Michael
13-06-1990 / Pieter
21-08-1987 / Pieter
01-10-1988 / Ruben

Sorted by Birthday ascending, Firstname ascending:
02-02-1977 / Arjan
04-11-1986 / Michael
21-08-1987 / Bastiaan
21-08-1987 / Pieter
01-10-1988 / Bryan
01-10-1988 / Ruben
13-06-1990 / Pieter

Performance Testing

I did some testing using the following code, where list1 is being sorted the usual way and list2 is being sorted using my code.

C#
public class TestClass
{
    public string Text { get; set; }
}

public static void Test()
{
    List<TestClass> list1 = new List<TestClass>();
    List<TestClass> list2 = new List<TestClass>();

    for (int index = 0; index < 1000; index++)
    {
        Guid item = Guid.NewGuid();
        list1.Add(new TestClass { Text = item.ToString() });
        list2.Add(new TestClass { Text = item.ToString() });
    }
    DateTime start1 = DateTime.Now;
    list1.Sort(delegate(TestClass p1, TestClass p2)
        { return p1.Text.CompareTo(p2.Text); });
    DateTime stop1 = DateTime.Now;
    TimeSpan diff1 = stop1 - start1;

    DateTime start2 = DateTime.Now;
    list2.Sort("Text asc");
    DateTime stop2 = DateTime.Now;
    TimeSpan diff2 = stop2 - start2;
}

Results

index = 1000;
diff1: TotalSeconds = 0.015625
diff2: TotalSeconds = 0.0625
4 times slower.

index = 10.000;
diff1: TotalSeconds = 0.03125
diff2: TotalSeconds = 0.46875
15 times slower.

index = 100.000;
diff1: TotalSeconds = 0.4375
diff2: TotalSeconds = 5.859375
13.4 times slower

History

  • 17-07-2008 09:50: Added some test results

License

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


Written By
Web Developer Evident Interactive
Netherlands Netherlands
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 5 Pin
AhsanS13-Mar-12 1:29
AhsanS13-Mar-12 1:29 
QuestionSource Code Moved? Pin
DaveAce1522-Apr-10 6:30
DaveAce1522-Apr-10 6:30 
GeneralCulture Sensitive Pin
Mifla Mashood12-May-09 22:48
Mifla Mashood12-May-09 22:48 
GeneralRe: Culture Sensitive Pin
Mifla Mashood12-May-09 23:20
Mifla Mashood12-May-09 23:20 
GeneralFor Feilds Pin
void leenux();22-Jan-09 11:08
void leenux();22-Jan-09 11:08 
GeneralSortDirection [modified] Pin
void leenux();21-Jan-09 16:21
void leenux();21-Jan-09 16:21 
GeneralGeneric multi-property sorting in .NET 3.0 Pin
Rich Visotcky15-Aug-08 8:07
Rich Visotcky15-Aug-08 8:07 
GeneralVery nice but... Pin
cmschick22-Jul-08 2:01
cmschick22-Jul-08 2:01 
GeneralRe: Very nice but... Pin
Arjan Pot31-Jul-08 8:54
Arjan Pot31-Jul-08 8:54 
GeneralLooks great Pin
Steven Berkovitz16-Jul-08 6:11
Steven Berkovitz16-Jul-08 6:11 
GeneralRe: Looks great Pin
Bryan Sumter16-Jul-08 6:30
Bryan Sumter16-Jul-08 6:30 
GeneralRe: Looks great Pin
Bryan Sumter16-Jul-08 22:04
Bryan Sumter16-Jul-08 22:04 

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

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