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

Generic List Sort Function

By , 16 Jul 2008
 

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.

/// <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!

    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)

About the Author

Bryan Sumter
Web Developer Evident Interactive
Netherlands Netherlands
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
Hint: For improved responsiveness ensure Javascript is enabled and choose 'Normal' from the Layout dropdown and hit 'Update'.
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 5memberAhsanS13 Mar '12 - 1:29 
QuestionSource Code Moved?memberDaveAce1522 Apr '10 - 6:30 
GeneralCulture Sensitivemembermifla8012 May '09 - 22:48 
GeneralRe: Culture Sensitivemembermifla8012 May '09 - 23:20 
I guess changing the current thread's culture helps, any ideas are most welcome
GeneralFor Feildsmembervoid leenux();22 Jan '09 - 11:08 
GeneralSortDirection [modified]membervoid leenux();21 Jan '09 - 16:21 
GeneralGeneric multi-property sorting in .NET 3.0memberRich Visotcky15 Aug '08 - 8:07 
GeneralVery nice but...membercmschick22 Jul '08 - 2:01 
GeneralRe: Very nice but...memberArjan Pot31 Jul '08 - 8:54 
GeneralLooks greatmemberSteven Berkovitz16 Jul '08 - 6:11 
GeneralRe: Looks greatmemberraiden8816 Jul '08 - 6:30 
GeneralRe: Looks greatmemberBryan Sumter16 Jul '08 - 22:04 

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

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130523.1 | Last Updated 16 Jul 2008
Article Copyright 2008 by Bryan Sumter
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid