Best way to sort a list by any property

My method gets all the DataTables parameters to sort the table on click. I am calling this method from the controller of each page list. I am looking for the best way to do this, as a general method for all types: string , int , decimal , double , bool ( null or not ). But I cannot find it.

My current code:

public List<T> OrderingList<T>(List<T> list, DataTablesParam model)
{
    var iColumn = model.Order.FirstOrDefault().Column;
    var property = typeof(T).GetProperty(model.Columns.ToArray()[iColumn].Data);
    var param = Expression.Parameter(typeof(T));
    var final = Expression.Property(param, property);

    var isDirAsc = model.Order.FirstOrDefault().Dir.Equals("asc");

    if (property.PropertyType == typeof(string))
    {
        var lambda = Expression.Lambda<Func<T, string>>(final, param).Compile();
        return isDirAsc ? list.OrderBy(lambda).ToList() : list.OrderByDescending(lambda).ToList();
    }
    else if (property.PropertyType == typeof(int))
    {
        var lambda = Expression.Lambda<Func<T, int>>(final, param).Compile();
        return isDirAsc ? list.OrderBy(lambda).ToList() : list.OrderByDescending(lambda).ToList();
    }
    else if (property.PropertyType == typeof(bool))
    {
        var lambda = Expression.Lambda<Func<T, bool>>(final, param).Compile();
        return isDirAsc ? list.OrderBy(lambda).ToList() : list.OrderByDescending(lambda).ToList();
    }
    else if (property.PropertyType == typeof(decimal))
    {
        var lambda = Expression.Lambda<Func<T, decimal>>(final, param).Compile();
        return isDirAsc ? list.OrderBy(lambda).ToList() : list.OrderByDescending(lambda).ToList();
    }
    else if (property.PropertyType == typeof(double))
    {
        var lambda = Expression.Lambda<Func<T, double>>(final, param).Compile();
        return isDirAsc ? list.OrderBy(lambda).ToList() : list.OrderByDescending(lambda).ToList();
    }

    return list;
}

      


I want to do something like this: (But this code doesn't work)

public List<T> OrderingList<T>(List<T> list, DataTablesParam model)
{
    var iColumn = model.Order.FirstOrDefault().Column;
    var property = typeof(T).GetProperty(model.Columns.ToArray()[iColumn].Data);
    var param = Expression.Parameter(typeof(T));
    var final = Expression.Property(param, property);

    var isDirAsc = model.Order.FirstOrDefault().Dir.Equals("asc");

    var lambda = Expression.Lambda<Func<T, dynamic>>(final, param).Compile();
    return isDirAsc ? list.OrderBy(lambda).ToList() : list.OrderByDescending(lambda).ToList();
}

      


+3


source to share


5 answers


I found a better way to do this. I had to do 3 steps:

1 - add the "Linq Dynamic" package to the project:

Install-Package System.Linq.Dynamic.Library

      

2 - Import the package to the class:



using System.Linq.Dynamic;

      

3 - List orders by property line name:

list.OrderBy(stringPropertyName);                 //asc
list.OrderBy(stringPropertyName + " descending"); //des

      

It works great for me.

0


source


You can just call the method Enumerable.OrderBy

using reflection. This way you don't need to know the type at compile time. To do this, you just need to get this method and create a generic method using the property type:

private IEnumerable<T> Sort<T> (List<T> list, string propertyName)
{
    MethodInfo orderByMethod = typeof(Enumerable).GetMethods().First(mi => mi.Name == "OrderBy" && mi.GetParameters().Length == 2);

    PropertyInfo pi = typeof(T).GetProperty(propertyName);
    MethodInfo orderBy = orderByMethod.MakeGenericMethod(typeof(T), pi.PropertyType);

    ParameterExpression param = Expression.Parameter(typeof(T));
    Delegate accessor = Expression.Lambda(Expression.Property(param, pi), param).Compile();
    return (IEnumerable<T>)orderBy.Invoke(null, new object[] { lst, accessor });
}

      



Note that I've rendered stuff about your model to keep this method general enough. It can basically sort by any property in the list just by specifying the property name. Then your original method will look like this:

public List<T> OrderingList<T>(List<T> list, DataTablesParam model)
{
    var iColumn = model.Order.FirstOrDefault().Column;
    string propertyName = model.Columns.ToArray()[iColumn].Data;

    return Sort(list, propertyName).ToList();
}

      

+1


source


Besides just not being very general, your solution also requires a lot of extra memory, because you are copying a list from LINQ. You can avoid this by using List.Sort

.

I would do:

static void SortBy<T>(List<T> list, MemberInfo member, bool desc)
{
    Comparison<T> cmp = BuildComparer<T>(member, desc);
    list.Sort(cmp);
}

static Comparison<T> BuildComparer<T>(MemberInfo member, bool desc)
{
    var left = Expression.Parameter(typeof(T));
    var right = Expression.Parameter(typeof(T));

    Expression cmp = Expression.Call(
        Expression.MakeMemberAccess(desc ? right : left, member),
        "CompareTo",
        Type.EmptyTypes,
        Expression.MakeMemberAccess(desc ? left : right, member));

    return Expression.Lambda<Comparison<T>>(cmp, left, right).Compile();
}

      

0


source


It works great for me: (Thanks @Poke)

fooobar.com/questions/2243676 / ...

My last method:

private IEnumerable<T> Sort<T>(IEnumerable<T> list, string propertyName, bool isAsc)
{
    MethodInfo orderByMethod = typeof(Enumerable).GetMethods().First(mi => mi.Name == (isAsc ? "OrderBy" : "OrderByDescending") && mi.GetParameters().Length == 2);

    PropertyInfo pi = typeof(T).GetProperty(propertyName);
    MethodInfo orderBy = orderByMethod.MakeGenericMethod(typeof(T), pi.PropertyType);

    ParameterExpression param = Expression.Parameter(typeof(T));
    Delegate accessor = Expression.Lambda(Expression.Call(param, pi.GetGetMethod()), param).Compile();
    return (IEnumerable<T>)orderBy.Invoke(null, new object[] { list, accessor });
}

      

0


source


Your suggested method almost works. You need to change two things to make it work:

public List<T> OrderingList<T>(List<T> list, DataTablesParam model)
{
    var iColumn = model.Order.FirstOrDefault().Column;
    var property = typeof(T).GetProperty(model.Columns.ToArray()[iColumn].Data);
    var param = Expression.Parameter(typeof(T), "p");
    Expression final = Expression.Property(param, property);

    // Boxing of value types
    if (property.PropertyType.IsValueType) {
        final = Expression.MakeUnary(ExpressionType.Convert, final, typeof(object));
    }

    var isDirAsc = model.Order.FirstOrDefault().Dir.Equals("asc");

    //                                     VVVVVV
    var lambda = Expression.Lambda<Func<T, object>>(final, param).Compile();
    return isDirAsc
        ? list.OrderBy(lambda).ToList()
        : list.OrderByDescending(lambda).ToList();
}

      

  • Instead of using the dynamic

    use object

    , as each type is object

    .
  • If you have a value type, you need a boxing operation, i.e. you have to pass the value to the object (object)i

    . This is accomplished using a unary transformation operation:

    Expression final = Expression.Property(param, property);
    if (property.PropertyType.IsValueType) {
        final = Expression.MakeUnary(ExpressionType.Convert, final, typeof(object));
    }
    
          

Note also that final is declared explicitly as Expression

, since the type of the expression can change from a property to a unary expression.

0


source







All Articles