C# 方法为“Select”的动态表达式树

C# 方法为“Select”的动态表达式树,c#,linq,reflection,expression,expression-trees,C#,Linq,Reflection,Expression,Expression Trees,我正在尝试使用表达式树构建以下lambda表达式-> info => info.event_objects.Select(x => x.object_info.contact_info) 我做了很多研究,找到了一些关于StackOverflow的答案。 帮助我建造了 info => info.event_objects.Any(x => x.object_info.contact_info.someBool == true) 如您所见,“Any”方法很容易

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

info => info.event_objects.Select(x => x.object_info.contact_info)
我做了很多研究,找到了一些关于StackOverflow的答案。 帮助我建造了

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);
主要问题在于“选择”方法。如果尝试将名称更改为“任意”,则会出现以下异常:

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");
    }
编辑:不幸的是,我尝试了问题的答案,但似乎不起作用

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

与“任何”不同,对于“选择”,有两个带有两个参数的重载:

选择源,函数选择器 选择源,函数选择器 获取项目,索引=>选择器lambda 因为您的代码已经依赖于深奥的知识,所以只需使用其中的第一个:

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

您可以通过反射避免找到正确的泛型方法重载,正如您已经注意到的那样,反射非常复杂且容易出错。Call方法重载一个用于静态方法,一个用于接受string methodName和Type[]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主体的表达式

测试:

结果:

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

可能重复的希望我的目标是明确的如果有人认为没有足够的信息,或者有些事情是不可理解的,我会尽我所能尽快通知你。@mjwills我认为选择的方法不止一种found@Emre,谢谢,我检查了这些,但不幸的是无法应用此答案,因为实现是不同的,或者我只是错过了一些东西:@AlexZholob 1。选择源,函数选择器2。SelectIE source,Func selector第二个Select方法是重载,它接受项,index=>selector lambdaThanks作为回复,现在,我需要使用:selectMethod=selectMethod.MakeGenericMethod使Select方法通用。但无论我将什么作为参数传递给MakeGenericMethod,都会得到异常。有什么想法吗?我试过输入和输出参数,但似乎不起作用,而且总是通过exception@Alex,Select是一个泛型方法,具有2个泛型参数。要获得构造的版本,需要用具体的类型替换这2个,比如int&bool以获得Select。这才是真正的好处。Select有2个通用参数->您需要使用两种类型的参数调用MakeGenericMethod W,它确实有效!谢谢你,伙计,我认为这是不可能的!
x => x.event_objects.Select(x1 => x1.object_info.contact_info)