关于c#:List < T > orderby的动态链接

Dynamic chaining of List orderby

本问题已经有最佳答案,请猛点这里访问。

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

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

就像是:
_list.orderBy(x=>x.prop1).thenBy(x=>x.prop2)

有没有办法动态构建它?


使用反射从字符串属性名称获取到PropertyInfo。然后,您可以使用PropertyInfo构建表达式树,以动态构建所有orderbys。获得表达式树后,将其编译为委托,(比如Func,IEnumerable>)将_list参数传递给此委托,它将为您提供另一个可枚举的有序结果。

要获取Enumerable上泛型方法的反射信息,请查看此帖子的答案:
在不使用GetMethods的情况下获取泛型方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
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中显示的样本结果

enter image description here


我不确定你是如何通过反射添加顺序(太懒于检查)但这里是伪代码的基本思想:

1
2
3
4
5
6
var query = list.OrderBy(properties.First());
bool first = true;
foreach(var property in properties.Skip(1))
{
    query = query.ThenBy(property);
}


你可以使用这样的东西:

1
2
3
4
5
6
7
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 - 我能想到的另一件事是使用反射和动态调用来做这件事。 ..如果不是真的必要,你不想做的事;)