用lambda表达式参数调用generics方法的reflection

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

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

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

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概念一起工作。

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

 //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 { 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 or have the target parameter IEnumerable //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 } 

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

更新:

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

谢谢,

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

 //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", "target"); //Get searchValues's T var searchValuesType = searchValues.GetType().GetGenericArguments().FirstOrDefault(); if (searchValuesType == null) throw new ArgumentException("Should be IEnumerable", "searchValues"); //Create ap parameter with the type T of the items in the -> target IEnumerable 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 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 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>(whereBody).Compile(); return whereLambda.Invoke(); } 

为了避免使用generics(因为在设计时不知道类型)你可以使用一些reflection并“手动”构建表达式

您需要通过在一个Where子句中定义“Contains”表达式来完成此操作:

 public IQueryable GetItemsFromContainsClause(Type type, IEnumerable items) { IUnitOfWork session = new SandstoneDbContext(); var method = this.GetType().GetMethod("ContainsExpression"); method = method.MakeGenericMethod(new[] { type }); var lambda = method.Invoke(null, new object[] { "Codigo", items }); var dbset = (session as DbContext).Set(type); var originalExpression = dbset.AsQueryable().Expression; var parameter = Expression.Parameter(type, ""); var callWhere = Expression.Call(typeof(Queryable), "Where", new[] { type }, originalExpression, (Expression)lambda); return dbset.AsQueryable().Provider.CreateQuery(callWhere); } public static Expression> ContainsExpression(string propertyName, IEnumerable values) { var parameterExp = Expression.Parameter(typeof(T), ""); var propertyExp = Expression.Property(parameterExp, propertyName); var someValue = Expression.Constant(values, typeof(IEnumerable)); var containsMethodExp = Expression.Call(typeof(Enumerable), "Contains", new[] { typeof(string) }, someValue, propertyExp); return Expression.Lambda>(containsMethodExp, parameterExp); } 

在这种情况下,“Codigo”是硬编码的,但它可以是获取您定义的类型的任何属性的参数。

您可以使用以下方法测试它:

 public void LambdaConversionBasicWithEmissor() { var cust= new Customer(); var items = new List() { "PETR", "VALE" }; var type = cust.GetType(); // Here you have your results from the database var result = GetItemsFromContainsClause(type, items); } 

您可以使用以下一组类来解决您的问题。

首先,我们需要创建一个Contains类,它将决定从源数组中选择哪些项。

 class Contains { public bool Value { get; set; } public Contains(object[] items, object item) { Value = (bool)(typeof(Enumerable).GetMethods() .Where(x => x.Name.Contains("Contains")) .First() .MakeGenericMethod(typeof(object)) .Invoke(items, new object[] { items, item })); } } 

然后我们需要创建一个Where类,它将用于根据将选择哪些项来形成谓词。 应该很清楚,在我们的例子中,我们将使用Contains类作为谓词方法。

 class Where { public object Value { get; set; } public Where(object[] items, object[] items2) { Value = typeof(Enumerable).GetMethods() .Where(x => x.Name.Contains("Where")) .First() .MakeGenericMethod(typeof(object)) .Invoke(items2, new object[] { items2, new Func(i => new Contains(items, i).Value) }); } } 

最后一步是简单地调用我们从Where类获得的结果,它实际上是Enumerable.WhereArrayIterator类型而不是List类型,因为Where Extension方法的结果是延迟执行的产物。

因此,我们需要通过调用其ToList扩展方法来创建非延迟对象,并获得我们的结果。

 class ToList { public List Value { get; set; } public ToList(object[] items, object[] items2) { var where = new Where(items, items2).Value; Value = (typeof(Enumerable).GetMethods() .Where(x => x.Name.Contains("ToList")) .First() .MakeGenericMethod(typeof(object)) .Invoke(where, new object[] { where })) as List; } } 

最后,您可以使用以下类简单地测试整个过程。

 class Program { static void Main() { var items = new object[] { 1, 2, 3, 4 }; var items2 = new object[] { 2, 3, 4, 5 }; new ToList(items, items2).Value.ForEach(x => Console.WriteLine(x)); Console.Read(); } }