用lambda表达式参数调用泛型方法的反射 [英] Reflection to call generic method with lambda expression parameter

查看:127
本文介绍了用lambda表达式参数调用泛型方法的反射的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在寻找一种方法来调用带有lambda表达式的泛型方法,该方法调用项目数组中的Contains。

I'm looking for a way to call a generic method with a lambda expression that calls Contains in an array of items.

在这种情况下,我使用Entity Framework Where方法,但该方案可以应用于其他IEnumerables。

In this case I'm using Entity Framework Where method, but the scenario could be applied in other IEnumerables.

我需要通过Reflection调用上述代码的最后一行,因此我可以使用任何类型和任何属性传递给Contains方法。

I need to call the last line of the above code through Reflection, so I can use any type and any property to pass to the Contains method.

var context = new TestEntities();

var items = new[] {100, 200, 400, 777}; //IN list (will be tested through Contains)
var type = typeof(MyType); 

context.Set(type).Where(e => items.Contains(e.Id)); //**What is equivalent to this line using Reflection?**

在研究中,我已经注意到我应该使用GetMethod,MakeGenericType和Expression来实现这一点,但我无法弄清楚如何去做。有了这个示例会非常有帮助,所以我可以理解Reflection如何与Lambda和Generic概念一起工作。

In research, I've noticed that I should use GetMethod, MakeGenericType and Expression to achieve that, but I couldn't figure out how to do it. It would be very helpful to have this sample so I can understand how Reflection works with Lambda and Generic concepts.

基本上,目标是编写一个正确版本的函数,如这:

Basically the objective is to write a correct version of a function like this:

//Return all items from a IEnumerable(target) that has at least one matching Property(propertyName) 
//with its value contained in a IEnumerable(possibleValues)
static IEnumerable GetFilteredList(IEnumerable target, string propertyName, IEnumerable searchValues)
{
    return target.Where(t => searchValues.Contains(t.propertyName));
    //Known the following:
    //1) This function intentionally can't be compiled
    //2) Where function can't be called directly from an untyped IEnumerable
    //3) t is not actually recognized as a Type, so I can't access its property 
    //4) The property "propertyName" in t should be accessed via Linq.Expressions or Reflection
    //5) Contains function can't be called directly from an untyped IEnumerable
}

//Testing environment
static void Main()
{
    var listOfPerson = new List<Person> { new Person {Id = 3}, new Person {Id = 1}, new Person {Id = 5} };
    var searchIds = new int[] { 1, 2, 3, 4 };

    //Requirement: The function must not be generic like GetFilteredList<Person> or have the target parameter IEnumerable<Person>
    //because the I need to pass different IEnumerable types, not known in compile-time
    var searchResult = GetFilteredList(listOfPerson, "Id", searchIds);

    foreach (var person in searchResult)
        Console.Write(" Found {0}", ((Person) person).Id);

    //Should output Found 3 Found 1
}

我不确定其他问题是否解决了这种情况,因为我不认为我能够清楚地理解表达式的工作方式。

I'm not sure if the other questions address this scenario, because I don't think I could clearly understand how Expressions work.

更新:

我不能使用泛型,因为我只有运行时要测试的类型和属性(在Contains中)。在第一个代码示例中,假设MyType在编译时不为人知。在第二个代码示例中,类型可以作为参数传递给GetFilteredList函数,或者可以通过Reflection(GetGenericArguments)获取。

I can't use Generics because I only have the type and the property to be tested (in Contains) at run-time. In the first code sample, suppose "MyType" is not known at compile time. In the second code sample, the type could be passed as a parameter to the GetFilteredList function or could be get via Reflection (GetGenericArguments).

谢谢,

推荐答案

经过广泛的研究和对表达式的大量研究之后,我可以自己编写解决方案。它当然可以改进,但完全符合我的要求。

After a wide research and a lot of study of Expressions I could write a solution myself. It certainly can be improved, but exactly fits my requirements. Hopefully it can help someone else.

//Return all items from a IEnumerable(target) that has at least one matching Property(propertyName) 
//with its value contained in a IEnumerable(possibleValues)
static IEnumerable GetFilteredList(IEnumerable target, string propertyName, IEnumerable searchValues)
{
    //Get target's T 
    var targetType = target.GetType().GetGenericArguments().FirstOrDefault();
    if (targetType == null)
        throw new ArgumentException("Should be IEnumerable<T>", "target");

    //Get searchValues's T
    var searchValuesType = searchValues.GetType().GetGenericArguments().FirstOrDefault();
    if (searchValuesType == null)
        throw new ArgumentException("Should be IEnumerable<T>", "searchValues");

    //Create a p parameter with the type T of the items in the -> target IEnumerable<T>
    var containsLambdaParameter = Expression.Parameter(targetType, "p");

    //Create a property accessor using the property name -> p.#propertyName#
    var property = Expression.Property(containsLambdaParameter, targetType, propertyName);

    //Create a constant with the -> IEnumerable<T> searchValues
    var searchValuesAsConstant = Expression.Constant(searchValues, searchValues.GetType());

    //Create a method call -> searchValues.Contains(p.Id)
    var containsBody = Expression.Call(typeof(Enumerable), "Contains", new[] { searchValuesType }, searchValuesAsConstant, property);

    //Create a lambda expression with the parameter p -> p => searchValues.Contains(p.Id)
    var containsLambda = Expression.Lambda(containsBody, containsLambdaParameter);

    //Create a constant with the -> IEnumerable<T> target
    var targetAsConstant = Expression.Constant(target, target.GetType());

    //Where(p => searchValues.Contains(p.Id))
    var whereBody = Expression.Call(typeof(Enumerable), "Where", new[] { targetType }, targetAsConstant, containsLambda);

    //target.Where(p => searchValues.Contains(p.Id))
    var whereLambda = Expression.Lambda<Func<IEnumerable>>(whereBody).Compile();

    return whereLambda.Invoke();
}

这篇关于用lambda表达式参数调用泛型方法的反射的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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