将 method/properties 链转换为表达式树

Convert method/properties chain into Expression Tree

我有这个类:

public class DiagnosticDetails
{
    public string PrimaryValue { get; set; }
    public Guid? Id { get; set; }
    public IEnumerable<DiagnosticValues> Values { get; set; }
}

public class DiagnosticValues
{
    public string Type { get; set; }
    public string Value { get; set; }
}

我想为 Values 中的每个字符串构造动态 OrderBy().ThenBy()...(此列表可以包含不同数量的字符串)因此它应该如下所示:

diagnosticDetails
    .OrderBy(detail => detail.Values.ElementAt(0).Value?.Trim())
    .ThenBy(detail => detail.Values.ElementAt(1).Value?.Trim())
    .ThenBy(detail => detail.Values.ElementAt(2).Value?.Trim())
    ...

但我无法将其转换为表达式树:

var result = diagnosticDetails.OrderBy(detail => detail.Values.ElementAt(0).Value?.Trim());
//var expressionTreeForResult = ???

这是我的代码:

   private List<DiagnosticDetails> SortByValue(List<DiagnosticDetails> diagnosticDetails)
    {
        IQueryable<DiagnosticDetails> queryableData = diagnosticDetails.AsQueryable();

        ParameterExpression pe = Expression.Parameter(typeof(DiagnosticDetails), "detail");


        //var predicate = diagnosticDetails.OrderBy(detail => detail.Values.ElementAt(0).Value?.Trim());
        //var predicateExpressionTree = ???


        MethodCallExpression orderByCallExpression = Expression.Call(
            typeof(Queryable),
            "OrderBy",
            new Type[] { queryableData.ElementType },
            queryableData.Expression,
            Expression.Lambda<Func<DiagnosticDetails, string>>(predicateExpressionTree, new ParameterExpression[] { pe }));

        var maxElementsInValues = diagnosticDetails.Select(dd => dd.Values.Count()).Max();

        for (int i = 1; i < maxElementsInValues; i++)
        {
            orderByCallExpression = Expression.Call(
                typeof(Queryable),
                "ThenBy",
                new Type[] { queryableData.ElementType },
                orderByCallExpression,
                Expression.Lambda<Func<DiagnosticDetails, string>>(predicateExpressionTree, new ParameterExpression[] { pe }));
        }

        var sortedDiagnosticDetails = Expression.Lambda<List<DiagnosticDetails>>(orderByCallExpression).Compile();

        return sortedDiagnosticDetails;
    }

如何将.OrderBy(detail => detail.Values.ElementAt(0).Value?.Trim())转化为表达式树?

正如 Ivan Stoev 在评论中提到的,在这种特殊情况下无需创建表达式树。 实施比较方法会容易得多:

    public static int CompareDiagnosticDetailsByValuesConsistently(DiagnosticDetails dd1, DiagnosticDetails dd2)
    {
        var maxDimension = Math.Max(dd1.Values.Count(), dd2.Values.Count());

        for (int i = 0; i < maxDimension; i++)
        {
            if (dd1.Values.ElementAtOrDefault(i)?.Value == null)
            {
                if (dd2.Values.ElementAtOrDefault(i)?.Value == null)
                    continue;

                return 1;
            }

            int result = dd1.Values.ElementAt(i).Value.CompareTo(dd2.Values.ElementAt(i).Value);
            if (result == 0)
                continue;

            return result;
        }

        return 0;
    }

并这样使用它:

diagnosticDetails.Sort(CompareDiagnosticDetailsByValuesConsistently);

但如果您仍想使用表达式树,此代码可满足您的需要:

private List<DiagnosticDetails> SortByAllValuesConsistently(List<DiagnosticDetails> diagnosticDetails)
    {
        /*
         diagnosticDetails.OrderBy(detail => detail.Values.ElementAt(0).Value)
            .ThenBy(detail => detail.Values.ElementAt(1).Value))
            .ThenBy(detail => detail.Values.ElementAt(2).Value))
            .ThenBy(detail => detail.Values.ElementAt(3).Value))
            ...

            for each value in detail.Values.

         */

        if (diagnosticDetails.IsNullOrEmpty())
            return diagnosticDetails;

        IQueryable<DiagnosticDetails> queryableData = diagnosticDetails.AsQueryable();

        // detail.Values
        ParameterExpression p = Expression.Parameter(typeof(DiagnosticDetails), "detail");
        MemberExpression prVs = Expression.Property(p, "Values");

        // detail.Values.ElementAt(0).
        ConstantExpression c0 = Expression.Constant(0, typeof(int));
        Expression callElAt = expressionTreeHelper.CallElementAt(prVs, c0);

        // detail.Values.ElementAt(0).Value
        MemberExpression prV = Expression.Property(callElAt, "Value");

        // detail => detail.Values.ElementAt(0).Value
        Delegate predicate = Expression.Lambda(prV,p).Compile();

        // diagnosticDetails.OrderBy(detail => detail.Values.ElementAt(0).Value)
        Expression orderByCallExpression = expressionTreeHelper.CallOrderBy(queryableData.Expression, predicate);

        var maxElementsInValues = diagnosticDetails.Select(dd => dd.Values.Count()).Max();

        for (int i = 1; i < maxElementsInValues; i++)
        {
            // detail.Values
            ParameterExpression pi = Expression.Parameter(typeof(DiagnosticDetails), "detail");
            MemberExpression prVsi = Expression.Property(pi, "Values");

            // detail.Values.ElementAt(i).
            ConstantExpression ci = Expression.Constant(i, typeof(int));
            Expression callElAti = expressionTreeHelper.CallElementAt(prVsi, ci);

            // detail.Values.ElementAt(i).Value
            MemberExpression prVi = Expression.Property(callElAti, "Value");

            // detail => detail.Values.ElementAt(i).Value
            Delegate predicateI = Expression.Lambda(prVi, pi).Compile();

            // orderByCallExpression.ThenBy(detail => detail.Values.ElementAt(0).Value)
            orderByCallExpression = expressionTreeHelper.CallThenBy(orderByCallExpression, predicateI);
        }

        // Get result
        var orderedList = (Func<IOrderedEnumerable<DiagnosticDetails>>)Expression.Lambda(orderByCallExpression).Compile();

        return orderedList().ToList();
    }

/// <remarks>
/// Look at  for more details
/// </remarks>
public class ExpressionTreeHelper : IExpressionTreeHelper
{
    public Expression CallElementAt(Expression collection, ConstantExpression constant)
    {
        Type cType = GetIEnumerableImpl(collection.Type);
        collection = Expression.Convert(collection, cType);

        Type elemType = cType.GetGenericArguments()[0];

        // Enumerable.ElementAt<T>(IEnumerable<T>, int index)
        MethodInfo elementAtMethod = (MethodInfo)GetGenericMethod(
            typeof(Enumerable),
            "ElementAt",
            new[] { elemType },
            new[] { collection.Type, constant.Type }, BindingFlags.Static);

        return Expression.Call(
            elementAtMethod,
            collection,
            constant);
    }

    public Expression CallOrderBy(Expression collection, Delegate predicate)
    {
        Type cType = GetIEnumerableImpl(collection.Type);
        collection = Expression.Convert(collection, cType);

        Type elemType = cType.GetGenericArguments()[0];
        Type predType = typeof(Func<,>).MakeGenericType(elemType, predicate.Method.ReturnType);

        // Enumerable.OrderBy<TSource, TKey>(IEnumerable<TSource>, Func<TSource,TKey>)
        MethodInfo orderByMethod = (MethodInfo)
            GetGenericMethod(
                typeof(Enumerable),
                "OrderBy",
                new[] { elemType, predicate.Method.ReturnType },
                new[] { cType, predType }, BindingFlags.Static);

        return Expression.Call(
            orderByMethod,
            collection,
            Expression.Constant(predicate));
    }

    public Expression CallThenBy(Expression collection, Delegate predicate)
    {
        Type inputType = GetIEnumerableImpl(collection.Type);

        Type elemType = inputType.GetGenericArguments()[0];
        Type predType = typeof(Func<,>).MakeGenericType(elemType, predicate.Method.ReturnType);

        // ! important convert to IOrderedEnumerable
        Type cType = typeof(IOrderedEnumerable<>).MakeGenericType(new Type[] { elemType });
        collection = Expression.Convert(collection, cType);

        // Enumerable.CallThenBy<TSource, TKey>(IEnumerable<TSource>, Func<TSource,TKey>)
        MethodInfo thenByMethod = (MethodInfo)
            GetGenericMethod(
                typeof(Enumerable),
                "ThenBy",
                new[] { elemType, predicate.Method.ReturnType },
                new[] { cType, predType }, BindingFlags.Static);

        return Expression.Call(
            thenByMethod,
            collection,
            Expression.Constant(predicate));
    }

    private MethodBase GetGenericMethod(Type type, string name, Type[] typeArgs, Type[] argTypes, BindingFlags flags)
    {
        int typeArity = typeArgs.Length;
        var methods = type.GetMethods()
            .Where(m => m.Name == name)
            .Where(m => m.GetGenericArguments().Length == typeArity)
            .Select(m => m.MakeGenericMethod(typeArgs));

        return Type.DefaultBinder.SelectMethod(flags, methods.ToArray(), argTypes, null);
    }

    private bool IsIEnumerable(Type type)
    {
        return type.IsGenericType
               && type.GetGenericTypeDefinition() == typeof(IEnumerable<>);
    }

    private Type GetIEnumerableImpl(Type type)
    {
        // Get IEnumerable implementation. Either type is IEnumerable<T> for some T, 
        // or it implements IEnumerable<T> for some T. We need to find the interface.
        if (IsIEnumerable(type))
            return type;
        Type[] t = type.FindInterfaces((m, o) => IsIEnumerable(m), null);
        Debug.Assert(t.Length == 1);
        return t[0];
    }
}

这样使用:

var sortedDetails = SortByAllValuesConsistently(diagnosticDetails)