Click here to Skip to main content
Click here to Skip to main content
Go to top

Generic List Sort Function

, 16 Jul 2008
Rate this:
Please Sign up or sign in to vote.
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:

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.

/// <span class="code-SummaryComment"><summary></span>
/// Sorts the specified list.
/// <span class="code-SummaryComment"></summary></span>
/// <span class="code-SummaryComment"><typeparam name="T"></typeparam></span>
/// <span class="code-SummaryComment"><param name="list">The list to be sorted.</param></span>
/// <span class="code-SummaryComment"><param name="sortExpression">The sort expression; format:</span>
/// @param1 [sortdirection], @param2 [sortdirection], @param3 [sortdirection].
/// Valid sortDirections are: asc, desc, ascending and descending.<span class="code-SummaryComment"></param></span>
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!

    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:

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.

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)

Share

About the Author

Bryan Sumter
Web Developer Evident Interactive
Netherlands Netherlands
No Biography provided

Comments and Discussions

 
GeneralSortDirection [modified] Pinmembervoid leenux();21-Jan-09 16:21 

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 | Mobile
Web01 | 2.8.140916.1 | Last Updated 16 Jul 2008
Article Copyright 2008 by Bryan Sumter
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid