Better way to Sort a List by any property

£可爱£侵袭症+ 提交于 2019-12-05 21:31:59

You can just call the Enumerable.OrderBy method using reflection. That way, you don’t have to know the type at compile-time. To do that, you just need to get the method, and create a generic method using the property’s 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 abstracted out the stuff about your model to keep this method generic enough. It can basically sort by any property on a list by just specifying the property name. Your original method would then 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();
}
Renan Cerqueira

It's works fine for me: (Thanks @Poke)

https://stackoverflow.com/a/31393168/5112444

My final 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 });
}

Your suggested method almost works. You need to change two things in order 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();
}
  1. Instead of using dynamic use object, since every type is an object.
  2. If you have a value type, you need a boxing operation, i.e. you must cast the value to object (object)i. This is done with a unary convert 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 expression type might change from property to unary expression.

Beyond just not being very generic, your solution also requires a lot of extra memory because you're copying the list with LINQ. You can avoid this 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();
}

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

1 - Add the package "Linq Dynamic" in project:

Install-Package System.Linq.Dynamic.Library

2 - Import the package in Class:

using System.Linq.Dynamic;

3 - Order list by the string name of property:

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

It work perfectly for me.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!