按任何 属性 对列表进行排序的更好方法
Better way to Sort a List by any property
我的方法接收所有 DataTables 参数以按单击的列排序 table。我从每个页面列表的控制器调用此方法。
我正在寻找一种更好的方法来执行此操作,例如针对所有类型的通用方法:string、int、decimal、double、bool(nullable 或 not).但是我找不到。
我当前的代码:
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();
}
- 而不是使用
dynamic
使用 object
,因为每个类型都是 object
.
如果你有一个值类型,你需要一个装箱操作,即你必须将值转换为对象(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
它非常适合我。
我的方法接收所有 DataTables 参数以按单击的列排序 table。我从每个页面列表的控制器调用此方法。 我正在寻找一种更好的方法来执行此操作,例如针对所有类型的通用方法:string、int、decimal、double、bool(nullable 或 not).但是我找不到。
我当前的代码:
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();
}
- 而不是使用
dynamic
使用object
,因为每个类型都是object
. 如果你有一个值类型,你需要一个装箱操作,即你必须将值转换为对象
(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
它非常适合我。