我正在编写一个列表排序扩展方法。 我的输入是列表和带有属性名称和排序方向的字符串。 这个字符串可以有多个属性,如下所示: “命名ASC,日期DESC”等。
我已经实现了字符串解析并使用了反射来从字符串中获取属性本身,但我现在所困扰的是如何动态链接orderby方法。
像这样的东西:_list.orderBy(x=>x.prop1).thenBy(x=>x.prop2)
等。
有没有办法动态构建它?
答案 0 :(得分:7)
使用反射从字符串属性名称获取到PropertyInfo。然后,您可以使用PropertyInfo构建表达式树,以动态构建所有orderbys。获得表达式树后,将其编译为委托,(比如Func,IEnumerable>)将_list参数传递给此委托,它将为您提供另一个可枚举的有序结果。
要获取Enumerable上泛型方法的反射信息,请查看此帖子的答案: Get a generic method without using GetMethods
public static class Helper
{
public static IEnumerable<T> BuildOrderBys<T>(
this IEnumerable<T> source,
params SortDescription[] properties)
{
if (properties == null || properties.Length == 0) return source;
var typeOfT = typeof (T);
Type t = typeOfT;
IOrderedEnumerable<T> result = null;
var thenBy = false;
foreach (var item in properties
.Select(prop => new {PropertyInfo = t.GetProperty(prop.PropertyName), prop.Direction}))
{
var oExpr = Expression.Parameter(typeOfT, "o");
var propertyInfo = item.PropertyInfo;
var propertyType = propertyInfo.PropertyType;
var isAscending = item.Direction == ListSortDirection.Ascending;
if (thenBy)
{
var prevExpr = Expression.Parameter(typeof (IOrderedEnumerable<T>), "prevExpr");
var expr1 = Expression.Lambda<Func<IOrderedEnumerable<T>, IOrderedEnumerable<T>>>(
Expression.Call(
(isAscending ? thenByMethod : thenByDescendingMethod).MakeGenericMethod(typeOfT, propertyType),
prevExpr,
Expression.Lambda(
typeof (Func<,>).MakeGenericType(typeOfT, propertyType),
Expression.MakeMemberAccess(oExpr, propertyInfo),
oExpr)
),
prevExpr)
.Compile();
result = expr1(result);
}
else
{
var prevExpr = Expression.Parameter(typeof (IEnumerable<T>), "prevExpr");
var expr1 = Expression.Lambda<Func<IEnumerable<T>, IOrderedEnumerable<T>>>(
Expression.Call(
(isAscending ? orderByMethod : orderByDescendingMethod).MakeGenericMethod(typeOfT, propertyType),
prevExpr,
Expression.Lambda(
typeof (Func<,>).MakeGenericType(typeOfT, propertyType),
Expression.MakeMemberAccess(oExpr, propertyInfo),
oExpr)
),
prevExpr)
.Compile();
result = expr1(source);
thenBy = true;
}
}
return result;
}
private static MethodInfo orderByMethod =
MethodOf(() => Enumerable.OrderBy(default(IEnumerable<object>), default(Func<object, object>)))
.GetGenericMethodDefinition();
private static MethodInfo orderByDescendingMethod =
MethodOf(() => Enumerable.OrderByDescending(default(IEnumerable<object>), default(Func<object, object>)))
.GetGenericMethodDefinition();
private static MethodInfo thenByMethod =
MethodOf(() => Enumerable.ThenBy(default(IOrderedEnumerable<object>), default(Func<object, object>)))
.GetGenericMethodDefinition();
private static MethodInfo thenByDescendingMethod =
MethodOf(() => Enumerable.ThenByDescending(default(IOrderedEnumerable<object>), default(Func<object, object>)))
.GetGenericMethodDefinition();
public static MethodInfo MethodOf<T>(Expression<Func<T>> method)
{
MethodCallExpression mce = (MethodCallExpression) method.Body;
MethodInfo mi = mce.Method;
return mi;
}
}
public static class Sample
{
private static void Main()
{
var data = new List<Customer>
{
new Customer {ID = 3, Name = "a"},
new Customer {ID = 3, Name = "c"},
new Customer {ID = 4},
new Customer {ID = 3, Name = "b"},
new Customer {ID = 2}
};
var result = data.BuildOrderBys(
new SortDescription("ID", ListSortDirection.Ascending),
new SortDescription("Name", ListSortDirection.Ascending)
).Dump();
}
}
public class Customer
{
public int ID { get; set; }
public string Name { get; set; }
}
LinqPad中显示的样本结果
答案 1 :(得分:1)
我不确定你是如何通过反射添加顺序的(太懒了检查)但这里是伪代码的基本思想:
var query = list.OrderBy(properties.First());
bool first = true;
foreach(var property in properties.Skip(1))
{
query = query.ThenBy(property);
}
答案 2 :(得分:0)
您可以使用以下内容:
var query = _list.OrderBy(x=>x.prop1);
if (shouldOrderByProp2Too)
query = query.ThenBy(x=>x.prop2);
if (shouldOrderByProp3Too)
query = query.ThenBy(x=>x.prop3);
// ...
// then use query the way you had your code
征求意见:
在这种情况下,我会对列表中的对象进行实现IComparable
,然后对其进行排序/或使用动态检查这一点的Compareres - 我能想到的另一件事就是用反射和动态来做这件事 - 调用...如果不是真的必要的话,不是你想要做的事情;)