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

Better way to Sort a List by any property

我的方法接收所有 DataTables 参数以按单击的列排序 table。我从每个页面列表的控制器调用此方法。 我正在寻找一种更好的方法来执行此操作,例如针对所有类型的通用方法:stringintdecimaldoubleboolnullablenot).但是我找不到。

我当前的代码:

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

您可以使用反射调用 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();
}

除了不是很通用之外,您的解决方案还需要大量额外内存,因为您要使用 LINQ 复制列表。您可以使用 List.Sort.

避免这种情况

我会做:

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

它对我来说很好用:(感谢@Poke)

我的最终方法:

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

你推荐的方法差不多可以用了。您需要更改两件事才能使其正常工作:

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

另请注意,final 被显式声明为 Expression,因为表达式类型可能从 属性 更改为一元表达式。

我找到了更好的方法。我必须执行 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

它非常适合我。