通过任何属性对列表进行排序的更好方法

时间:2015-07-13 20:11:41

标签: c# generics reflection expression func

我的方法接收所有DataTables参数以按列单击对表进行排序。我从每个页面列表的控制器调用此方法。 我正在寻找一种更好的方法,像所有类型的通用方法一样:字符串 int 十进制 double bool 可空)。但是我无法找到它。

我目前的代码:

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;
}

我想做这样的事情:(但这段代码不起作用)

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();
}

5 个答案:

答案 0 :(得分:1)

您只需使用反射调用Enumerable.OrderBy方法即可。这样,您不必在编译时知道类型。为此,您只需要获取方法,并使用属性的类型创建泛型方法:

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

请注意,我抽象出了有关模型的内容,以保持此方法的通用性。它基本上可以通过指定属性名称按列表中的任何属性进行排序。您的原始方法将如下所示:

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 :(得分:0)

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();
}

答案 2 :(得分:0)

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

答案 3 :(得分:0)

您建议的方法几乎可行。您需要更改两件事才能使其正常工作:

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. 而不是dynamic使用object,因为每种类型都是object
  2. 如果您有值类型,则需要装箱操作,即必须将值强制转换为对象(object)i。这是通过一元转换操作完成的:

    Expression final = Expression.Property(param, property);
    if (property.PropertyType.IsValueType) {
        final = Expression.MakeUnary(ExpressionType.Convert, final, typeof(object));
    }
    
  3. 另请注意,final显式声明为Expression,因为表达式类型可能会从属性更改为一元表达式。

答案 4 :(得分:0)

我找到了一个更好的方法来做到这一点。我必须做3个步骤:

1 - 在项目中添加“Linq Dynamic”软件包:

Install-Package System.Linq.Dynamic.Library

2 - 在Class中导入包:

using System.Linq.Dynamic;

3 - 按字符串名称列出的订单列表:

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

它非常适合我。