views:

9663

answers:

8

I have the name of the "sort by property" in a string. I will need to use Lambda/Linq to sort the list of objects.

Ex:

public class Employee
{
  public string FirstName {set; get;}
  public string LastName {set; get;}
  public DateTime DOB {set; get;}
}


public void Sort(ref List<Employee> list, string sortBy, string sortDirection)
{
  //Example data:
  //sortBy = "FirstName"
  //sortDirection = "ASC" or "DESC"

  var sort = list.

  if (sortBy == "FirstName")
  {
    list = list.OrderBy(x => x.FirstName).toList();    
  }

}
  1. Instead of using a bunch of ifs to check the fieldname (sortBy), is there a cleaner way of doing the sorting
  2. Is sort aware of datatype?
A: 

Answer for 1.:

You should be able to manually build an expression tree that can be passed into OrderBy using the name as a string. Or you could use reflection as suggested in another answer, which might be less work.

Edit: Here is a working example of building an expression tree manually. (Sorting on X.Value, when only knowing the name "Value" of the property). You could (should) build a generic method for doing it.

using System;
using System.Linq;
using System.Linq.Expressions;

class Program
{
    private static readonly Random rand = new Random();
    static void Main(string[] args)
    {
        var randX = from n in Enumerable.Range(0, 100)
                    select new X { Value = rand.Next(1000) };

        ParameterExpression pe = Expression.Parameter(typeof(X), "value");
        var expression = Expression.Property(pe, "Value");
        var exp = Expression.Lambda<Func<X, int>>(expression, pe).Compile();

        foreach (var n in randX.OrderBy(exp))
            Console.WriteLine(n.Value);
    }

    public class X
    {
        public int Value { get; set; }
    }
}

Building an expression tree requires you to know the particpating types, however. That might or might not be a problem in your usage scenario. If you don't know what type you should be sorting on, it will propably be easier using reflection.

Answer for 2.:

Yes, since Comparer<T>.Default will be used for the comparison, if you do not explicitly define the comparer.

driis
Do you have an example of building an expression tree to be passed into OrderBy?
DotnetDude
+4  A: 

You could use Reflection to get the value of the property.

list = list.OrderBy( x => TypeHelper.GetPropertyValue( x, sortBy ) )
           .ToList();

Where TypeHelper has a static method like:

public static class TypeHelper
{
    public static object GetPropertyValue( object obj, string name )
    {
        return obj == null ? null : obj.GetType()
                                       .GetProperty( name )
                                       .GetValue( obj, null );
    }
}

You might also want to look at Dynamic LINQ from the VS2008 Samples library. You could use the IEnumerable extension to cast the List as an IQueryable and then use the Dynamic link OrderBy extension.

 list = list.AsQueryable().OrderBy( sortBy + " " + sortDirection );
tvanfosson
While this does solve his problem, we might want to steer him away from using a string to sort it. Good answer none-the-less.
Samuel
You can use Dynamic linq without Linq to Sql to do what he needs...I love it
JoshBerke
Sure. You can convert it to IQueryable. Didn't think about that. Updating my answer.
tvanfosson
+4  A: 

One thing you could do it change Sort so it makes better use of lambdas.

public enum SortDirection { Ascending, Decending }
public void Sort<TKey>(ref List<Employee> list,
                       Func<Employee, TKey> sorter, SortDirection direction)
{
  if (direction == SortDirection.Ascending)
    list = list.OrderBy(sorter);
  else
    list = list.OrderByDescending(sorter);
}

Now you can specify the field to sort when calling the Sort method.

Sort(employees, e => e.DOB, SortDirection.Decending);
Samuel
Since the sort column is in a string you'd still need a switch/if-else blocks to determine which function to pass it.
tvanfosson
You can't make that assumption. Who knows how his code calls it.
Samuel
He stated in the question that the "sort by property" is in a string. I'm just going by his question.
tvanfosson
True, but maybe it's only in string form because he didn't know he could use lambdas to sort it like this?
Samuel
I think it's more likely because it's coming from a sort control on a web page that passes the sort column back as a string parameter. That would be my use case, anyway.
tvanfosson
@tvanfosson - You are right, I have a custom control that has the order and the field name as a string
DotnetDude
A: 

Sort uses the IComparable interface, if the type implements it. And you can avoid the ifs by implementing a custom IComparer:

class EmpComp : IComparer<Employee>
{
    string fieldName;
    public EmpComp(string fieldName)
    {
        this.fieldName = fieldName;
    }

    public int Compare(Employee x, Employee y)
    {
        // compare x.fieldName and y.fieldName
    }
}

and then

list.Sort(new EmpComp(sortBy));
Serguei
FYI: Sort is a method of List<T> and is not a Linq extension.
Serguei
+1  A: 

You could use reflection to access the property.

public void Sort(List<Employee> list, String sortBy, String sortDirection)
{
   PropertyInfo property = list.GetType().GetGenericArguments()[0].
                                GetType().GetProperty(sortBy);

   if (sortDirection == "ASC")
   {
      return list.OrderBy(e => proeprty.GetValue(e, null));
   }
   if (sortDirection == "DESC")
   {
      return list.OrderByDescending(e => proeprty.GetValue(e, null));
   }
   else
   {
      throw new ArgumentOutOfRangeException();
   }
}

Notes

  1. Why do you pass the list by reference?
  2. You should use a enum for the sort direction.
  3. You could get a much cleaner solution if you would pass a lambda expression specifying the property to sort by instead of the proeprty name as a string.
  4. In my example list == null will cause a NullReferenceException, you should catch this case.
Daniel Brückner
+2  A: 

Building the order by expression can be read here

Shamelessly stolen from the page in link:

// First we define the parameter that we are going to use
// in our OrderBy clause. This is the same as "(person =>"
// in the example above.
var param = Expression.Parameter(typeof(Person), "person");

// Now we'll make our lambda function that returns the
// "DateOfBirth" property by it's name.
var mySortExpression = Expression.Lambda<Func<Person, object>>(Expression.Property(param, "DateOfBirth"), param);

// Now I can sort my people list.
Person[] sortedPeople = people.OrderBy(mySortExpression).ToArray();
Rashack
There are problems associated with this: DateTime sort.
CrazyEnigma
A: 

Here is example of what you need and what driis talked about in his first answer 1: http://vgermain.wordpress.com/2008/09/23/linq-to-sql-how-to-order-with-a-string-typed-expression/

Kamarey
A: 

See below. (Corrected code to work on VS2008 as well).

andras