如何创建一个前pression树叫了IEnumerable< TSource>。任何(...)? [英] How do I create an expression tree calling IEnumerable<TSource>.Any(...)?

查看:137
本文介绍了如何创建一个前pression树叫了IEnumerable< TSource>。任何(...)?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想创建一个重新$ P $的前pression树psents如下:

I am trying to create an expression tree that represents the following:

myObject.childObjectCollection.Any(i => i.Name == "name");

缩短为清楚起见,我有以下几点:

Shortened for clarity, I have the following:

//'myObject.childObjectCollection' is represented here by 'propertyExp'
//'i => i.Name == "name"' is represented here by 'predicateExp'
//but I am struggling with the Any() method reference - if I make the parent method
//non-generic Expression.Call() fails but, as per below, if i use <T> the 
//MethodInfo object is always null - I can't get a reference to it

private static MethodCallExpression GetAnyExpression<T>(MemberExpression propertyExp, Expression predicateExp)
{
    MethodInfo method = typeof(Enumerable).GetMethod("Any", new[]{ typeof(Func<IEnumerable<T>, Boolean>)});
    return Expression.Call(propertyExp, method, predicateExp);
}

我是什么做错了吗?任何人有什么建议?

What am I doing wrong? Anyone have any suggestions?

推荐答案

有几件事情不对的你打算如何吧。

There are several things wrong with how you're going about it.

  1. 您正在混合的抽象水平。 t参数为 GetAnyEx pression&LT; T&GT; 可能会有所不同,以用于实例化类型参数 propertyExp.Type 。在T形参数一步在抽象栈接近编译时间 - 除非你调用 GetAnyEx pression&LT; T&GT; 通过反射,它会在确定编译时间 - 但镶嵌在传递 propertyExp前pression类型在运行时确定。您传递的predicate为防爆pression 也是一种抽象的mixup - 这是下一个焦点

  1. You're mixing abstraction levels. The T parameter to GetAnyExpression<T> could be different to the type parameter used to instantiate propertyExp.Type. The T type parameter is one step closer in the abstraction stack to compile time - unless you're calling GetAnyExpression<T> via reflection, it will be determined at compile time - but the type embedded in the expression passed as propertyExp is determined at runtime. Your passing of the predicate as an Expression is also an abstraction mixup - which is the next point.

在predicate要传递到 GetAnyEx pression 应该是一个代表的价值,而不是防爆pression 任何类型的,因为你试图调用 Enumerable.Any&LT; T&GT; 。如果你试图调用的前pression树版本任何,那么你应该通过一个 LambdaEx pression 相反,你会被引用,而且是罕见的情况下,你可能会在传递一个更具体的类型比前pression,这使我对我的下一个点是合理的一个。

The predicate you are passing to GetAnyExpression should be a delegate value, not an Expression of any kind, since you're trying to call Enumerable.Any<T>. If you were trying to call an expression-tree version of Any, then you ought to pass a LambdaExpression instead, which you would be quoting, and is one of the rare cases where you might be justified in passing a more specific type than Expression, which leads me to my next point.

在一般情况下,你应该通过周围防爆pression 值。当与前pression树一般营运 - 这适用于所有类型的编译器,而不仅仅是LINQ和它的朋友 - 你应该在某种程度上这是不可知这样做的节点树的直接组成你的工作用。您现在的 presuming 你调用任何 MemberEx pression ,但你实际上并没有需要知道,你正在处理一个 MemberEx pression ,只是一个防爆pression 键入的IEnumerable&LT的;&GT; 。这是一个常见的​​错误的人不熟悉的编译器的AST的基本知识。 <一href="http://weblogs.asp.net/fbouma/archive/2007/10/03/developing-linq-to-llblgen-pro-day-5.aspx">Frans鲍马反复犯同样的错误,当他第一次开始与前pression树木的工作 - 思想在特殊情况下。普遍认为。你可以节省自己很多麻烦,在中期和长期的。

In general, you should pass around Expression values. When working with expression trees in general - and this applies across all kinds of compilers, not just LINQ and its friends - you should do so in a way that's agnostic as to the immediate composition of the node tree you're working with. You are presuming that you're calling Any on a MemberExpression, but you don't actually need to know that you're dealing with a MemberExpression, just an Expression of type some instantiation of IEnumerable<>. This is a common mistake for people not familiar with the basics of compiler ASTs. Frans Bouma repeatedly made the same mistake when he first started working with expression trees - thinking in special cases. Think generally. You'll save yourself a lot of hassle in the medium and longer term.

这里来你的问题的肉(虽然你,如果你已经得到了过去,第二次,或许第一问题将有位) - 你需要找到任何方法的适当的一般过载,然后实例它与正确的类型。反思不为您提供一个简单的在这里;你需要遍历,找到一个合适的版本。

And here comes the meat of your problem (though the second and probably first issues would have bit you if you had gotten past it) - you need to find the appropriate generic overload of the Any method, and then instantiate it with the correct type. Reflection doesn't provide you with an easy out here; you need to iterate through and find an appropriate version.

因此​​,将它分解:你需要找到一个通用的方法(任何)。这里有一个效用函数,做的是:

So, breaking it down: you need to find a generic method (Any). Here's a utility function that does that:

static 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);
}

但是,它需要的类型参数和正确的参数类型。获得从你的 propertyExp 防爆pression 不完全是微不足道的,因为防爆pression 可以是一个名单,其中,T&GT; 键入,或一些其他类型的,但我们需要找到的IEnumerable&LT; T&GT; 实例化,并获得它的类型参数。我已经封装到这几个功能:

However, it requires the type arguments and the correct argument types. Getting that from your propertyExp Expression isn't entirely trivial, because the Expression may be of a List<T> type, or some other type, but we need to find the IEnumerable<T> instantiation and get its type argument. I've encapsulated that into a couple of functions:

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

static 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];
}

所以,对于任何键入,我们现在可以拉的IEnumerable&LT; T&GT; 实例化出来的 - 和断言,如果没有(精确地)之一。

So, given any Type, we can now pull the IEnumerable<T> instantiation out of it - and assert if there isn't (exactly) one.

随着这项工作的出路,解决真正的问题不是太难。我已经改名为你的方法CallAny,并改变所建议的参数类型:

With that work out of the way, solving the real problem isn't too difficult. I've renamed your method to CallAny, and changed the parameter types as suggested:

static Expression CallAny(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, typeof(bool));

    // Enumerable.Any<T>(IEnumerable<T>, Func<T,bool>)
    MethodInfo anyMethod = (MethodInfo)
        GetGenericMethod(typeof(Enumerable), "Any", new[] { elemType }, 
            new[] { cType, predType }, BindingFlags.Static);

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

下面是一个主要()程序,它使用上述所有code和验证它为一个简单的情况:

Here's a Main() routine which uses all the above code and verifies that it works for a trivial case:

static void Main()
{
    // sample
    List<string> strings = new List<string> { "foo", "bar", "baz" };

    // Trivial predicate: x => x.StartsWith("b")
    ParameterExpression p = Expression.Parameter(typeof(string), "item");
    Delegate predicate = Expression.Lambda(
        Expression.Call(
            p,
            typeof(string).GetMethod("StartsWith", new[] { typeof(string) }),
            Expression.Constant("b")),
        p).Compile();

    Expression anyCall = CallAny(
        Expression.Constant(strings),
        predicate);

    // now test it.
    Func<bool> a = (Func<bool>) Expression.Lambda(anyCall).Compile();
    Console.WriteLine("Found? {0}", a());
    Console.ReadLine();
}

这篇关于如何创建一个前pression树叫了IEnumerable&LT; TSource&GT;。任何(...)?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆