【问题标题】:Dynamic expression tree with method 'Select'具有“选择”方法的动态表达式树
【发布时间】:2018-08-17 21:41:00
【问题描述】:

我正在尝试使用表达式树构建以下 lambda 表达式 ->

info => info.event_objects.Select(x => x.object_info.contact_info)

我研究了很多,并在 StackOverflow 上找到了一些答案。 This one 帮助我构建了

info => 
     info.event_objects.Any(x => x.object_info.contact_info.someBool == true)

如您所见,'Any' 方法很容易获得。

var anyMethod = typeof(Enumerable).GetMethods().Single(m => m.Name == "Any" 
&& m.GetParameters().Length == 2);
anyMethod = anyMethod.MakeGenericMethod(childType);

主要问题在于“选择”方法。如果您尝试将名称“Any”更改为“Select”,则会出现以下异常:

var selectMethod = typeof(Enumerable).GetMethods().Single(m => m.Name == 
"Select" && m.GetParameters().Length == 2);
selectMethod = selectMethod.MakeGenericMethod(childType);

附加信息:序列包含多个匹配元素

我尝试过的另一种方法:

MethodInfo selectMethod = null;
foreach (MethodInfo m in typeof(Enumerable).GetMethods().Where(m => m.Name 
  == "Select"))
    foreach (ParameterInfo p in m.GetParameters().Where(p => 
           p.Name.Equals("selector")))
        if (p.ParameterType.GetGenericArguments().Count() == 2)
            selectMethod = (MethodInfo)p.Member;

这似乎可行,但后来我在这里遇到了异常:

navigationPropertyPredicate = Expression.Call(selectMethod, parameter, 
navigationPropertyPredicate);

 Additional information: Method 
 System.Collections.Generic.IEnumerable`1[TResult] Select[TSource,TResult] 
 (System.Collections.Generic.IEnumerable`1[TSource], 
 System.Func`2[TSource,TResult]) is a generic method definition> 

之后,我尝试使用:

selectMethod = selectMethod.MakeGenericMethod(typeof(event_objects), 
typeof(contact_info));

其实也没用。

这是我的完整代码

 public static Expression GetNavigationPropertyExpression(Expression parameter, params string[] properties)
    {
        Expression resultExpression = null;
        Expression childParameter, navigationPropertyPredicate;
        Type childType = null;

        if (properties.Count() > 1)
        {
            //build path
            parameter = Expression.Property(parameter, properties[0]);
            var isCollection = typeof(IEnumerable).IsAssignableFrom(parameter.Type);
            //if it´s a collection we later need to use the predicate in the methodexpressioncall
            if (isCollection)
            {
                childType = parameter.Type.GetGenericArguments()[0];
                childParameter = Expression.Parameter(childType, "x");
            }
            else
            {
                childParameter = parameter;
            }
            //skip current property and get navigation property expression recursivly
            var innerProperties = properties.Skip(1).ToArray();
            navigationPropertyPredicate = GetNavigationPropertyExpression(childParameter, innerProperties);
            if (isCollection)
            {
                //var selectMethod = typeof(Enumerable).GetMethods().Single(m => m.Name == "Select" && m.GetParameters().Length == 2);
                //selectMethod = selectMethod.MakeGenericMethod(childType);
                MethodInfo selectMethod = null;
                foreach (MethodInfo m in typeof(Enumerable).GetMethods().Where(m => m.Name == "Select"))
                    foreach (ParameterInfo p in m.GetParameters().Where(p => p.Name.Equals("selector")))
                        if (p.ParameterType.GetGenericArguments().Count() == 2)
                            selectMethod = (MethodInfo)p.Member;

                navigationPropertyPredicate = Expression.Call(selectMethod, parameter, navigationPropertyPredicate);
                resultExpression = MakeLambda(parameter, navigationPropertyPredicate);
            }
            else
            {
                resultExpression = navigationPropertyPredicate;
            }
        }
        else
        {
            var childProperty = parameter.Type.GetProperty(properties[0]);
            var left = Expression.Property(parameter, childProperty);
            var right = Expression.Constant(true, typeof(bool));
            navigationPropertyPredicate = Expression.Lambda(left);
            resultExpression = MakeLambda(parameter, navigationPropertyPredicate);
        }
        return resultExpression;
    }


    private static Expression MakeLambda(Expression parameter, Expression predicate)
    {
        var resultParameterVisitor = new ParameterVisitor();
        resultParameterVisitor.Visit(parameter);
        var resultParameter = resultParameterVisitor.Parameter;
        return Expression.Lambda(predicate, (ParameterExpression)resultParameter);
    }

    private class ParameterVisitor : ExpressionVisitor
    {
        public Expression Parameter
        {
            get;
            private set;
        }
        protected override Expression VisitParameter(ParameterExpression node)
        {
            Parameter = node;
            return node;
        }
    }


    [TestMethod]
    public void TestDynamicExpression()
    {
        var parameter = Expression.Parameter(typeof(event_info), "x");
        var expression = GetNavigationPropertyExpression(parameter, "event_objects", "object_info", "contact_info");
    }

编辑:不幸的是,我尝试了this 问题的答案,但它似乎不起作用

【问题讨论】:

  • 希望我的目的是明确的,如果有人认为没有足够的信息,或者有什么无法理解的,我会尽我所能尽快通知你。
  • @mjwills 我认为找到的选择方法不止一种
  • @Emre,谢谢,我检查了这些,但不幸的是无法应用这个答案,因为实现不同,或者我只是错过了一些东西:(
  • @AlexZholob 1. Select<TS, TR>(IE<TS> source, Func<TS, TR> selector) 2. Select<TS, TR>(IE<TS> source, Func<TS, int, TR> selector) 第二个 Select 方法是采用“(item, index) =>”选择器 lambda 的重载

标签: c# linq reflection expression expression-trees


【解决方案1】:

您可以通过使用两个Expression.Call 方法重载之一(一个用于static,一个用于instance 方法)避免通过反射找到正确的泛型方法重载(正如您已经注意到的那样,这很复杂且容易出错) ) 接受string methodNameType[] typeArguments

此外,由于表达式和 lambda 表达式构建缺乏明确的分离,当前的实现过于复杂并包含其他问题。

这是一个正确的工作实现:

public static LambdaExpression GetNavigationPropertySelector(Type type, params string[] properties)
{
    return GetNavigationPropertySelector(type, properties, 0);
}

private static LambdaExpression GetNavigationPropertySelector(Type type, string[] properties, int depth)
{
    var parameter = Expression.Parameter(type, depth == 0 ? "x" : "x" + depth);
    var body = GetNavigationPropertyExpression(parameter, properties, depth);
    return Expression.Lambda(body, parameter);
}

private static Expression GetNavigationPropertyExpression(Expression source, string[] properties, int depth)
{
    if (depth >= properties.Length)
        return source;
    var property = Expression.Property(source, properties[depth]);
    if (typeof(IEnumerable).IsAssignableFrom(property.Type))
    {
        var elementType = property.Type.GetGenericArguments()[0];
        var elementSelector = GetNavigationPropertySelector(elementType, properties, depth + 1);
        return Expression.Call(
            typeof(Enumerable), "Select", new Type[] { elementType, elementSelector.Body.Type },
            property, elementSelector);
    }
    else
    {
        return GetNavigationPropertyExpression(property, properties, depth + 1);
    }
}

第一个是公共方法。它在内部使用接下来的两个私有方法递归地构建所需的 lambda。如您所见,我区分了构建 lambda 表达式和仅用作 lambda 主体的表达式。

测试:

var selector = GetNavigationPropertySelector(typeof(event_info), 
    "event_objects", "object_info", "contact_info");

结果:

x => x.event_objects.Select(x1 => x1.object_info.contact_info)

【讨论】:

  • 哇,真的好用!谢谢你,我认为这是不可能的!
【解决方案2】:

“附加信息:序列包含多个匹配元素”

与 "Any()" 不同,对于 "Select()" 有两个带有两个参数的重载:

  1. Select<TS, TR>(IE<TS> source, Func<TS, TR> selector)
  2. Select<TS, TR>(IE<TS> source, Func<TS, int, TR> selector) (采用“(item,index)=>”选择器lambda)

由于您的代码无论如何都已经依赖于“深奥的知识”,因此只需使用其中的第一个:

var selectMethod = typeof(Enumerable).GetMethods()
        .First(m => m.Name == nameof(Enumerable.Select) 
                 && m.GetParameters().Length == 2);

【讨论】:

  • 感谢您的回复,现在,我需要使用:selectMethod = selectMethod.MakeGenericMethod() 将选择方法设为通用。但是无论我作为参数传递给 MakeGenericMethod(),我都会遇到异常。有什么想法吗?
  • 输入输出参数我都试过了,但是好像不行,总是通过异常
  • @Alex, Select<TSource, TResult>() 是一个泛型方法,有 2 个泛型参数。要获得“构造”版本,您需要将这 2 个替换为具体类型,例如 int & bool(以获取 Select<int, bool>())。这实际上是 MakeGenericMethod(Type[] typeArguments) 的优点。 Select 有 2 个泛型参数 -> 您需要使用两个类型参数调用 MakeGenericMethod()
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-14
  • 1970-01-01
  • 2019-12-22
相关资源
最近更新 更多