List<T> orderby 的动态链接 [重复]

Posted

技术标签:

【中文标题】List<T> orderby 的动态链接 [重复]【英文标题】:Dynamic chaining of List<T> orderby [duplicate] 【发布时间】:2012-03-12 07:21:06 【问题描述】:

我正在编写一个列表排序扩展方法。 我的输入是列表和带有属性名称和排序方向的字符串。 该字符串可以具有多个属性,如下所示: “名称 ASC、日期 DESC”等。

我已经实现了字符串解析并使用反射从字符串中获取属性本身,但我现在坚持的是如何动态链接 orderby 方法。

类似: _list.orderBy(x=&gt;x.prop1).thenBy(x=&gt;x.prop2)

有没有办法动态构建它?

【问题讨论】:

见:***.com/a/233505/861716 【参考方案1】:

使用反射从字符串属性名中获取一个 PropertyInfo 的。然后,您可以使用 PropertyInfo 构建表达式树来动态构建所有 orderby。获得表达式树后,将其编译为委托,(例如 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中显示的样本结果

【讨论】:

在我的示例中,我递归地构建一个表达式并编译每个属性,但是您可以使用 Expression.Block 构建整个表达式树,然后只编译一次。 如果您想让某些属性降序排列,您可能还想扩展此示例以包含 bool[] 升序参数。 您的解决方案看起来非常好。谢谢你。只是一件事,您对集成对 ASC 和 DESC 排序的支持有什么想法吗?我又添加了两个 MethodInfo,但我找不到如何继续前进。 我根据您的要求更新了样本。 properties 参数现在是 System.ComponentModel.SortDescription[]。它包含属性名称和订单方向。【参考方案2】:

我不确定您如何通过反射添加订单(懒得检查),但这是伪代码的基本思想:

var query = list.OrderBy(properties.First());
bool first = true;
foreach(var property in properties.Skip(1))

    query = query.ThenBy(property);

【讨论】:

【参考方案3】:

你可以这样使用:

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 - 我能想到的唯一另一件事是使用反射和动态调用。 ..如果不是真的有必要,你不想做的事情;)

【讨论】:

感谢您的回答,但如果可能的话,我更喜欢适用于任何 N 个属性的方法。

以上是关于List<T> orderby 的动态链接 [重复]的主要内容,如果未能解决你的问题,请参考以下文章

如何动态指定 Linq OrderBy 参数? [复制]

在EF中构建自定义OrderBy表达式

C# 如何为 List<T> 动态分配内存?

MVC - 将动态添加的控件绑定到模型中的 List<T>

在c#中List<>的orderby方法如何使用?如何对一个类别的数据按照其某个属性降序排列?

LINQ to Entities OrderBy 表达式树