用lambda表达式参数调用泛型方法的反射
本文关键字:泛型方法 反射 调用 参数 lambda 表达式 | 更新日期: 2023-09-27 18:20:42
我正在寻找一种方法来调用一个具有lambda表达式的泛型方法,该表达式在一个项数组中调用Contains。
在这种情况下,我使用的是EntityFrameworkWhere方法,但该场景可以应用于其他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来实现这一点,但我不知道如何做到。如果有这个样本,我会很有帮助,这样我就可以理解反射是如何与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<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
}
我不确定其他问题是否解决了这种情况,因为我不认为我能清楚地理解表达式是如何工作的。
更新:
我不能使用泛型,因为我只有要在运行时测试的类型和属性(在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<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();
}
为了避免使用泛型(因为类型在设计时是未知的),您可以使用一些反射并"手动"构建表达式
您需要在Where子句中定义一个"Contains"表达式:
public IQueryable GetItemsFromContainsClause(Type type, IEnumerable<string> 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<Func<T, bool>> ContainsExpression<T>(string propertyName, IEnumerable<string> values)
{
var parameterExp = Expression.Parameter(typeof(T), "");
var propertyExp = Expression.Property(parameterExp, propertyName);
var someValue = Expression.Constant(values, typeof(IEnumerable<string>));
var containsMethodExp = Expression.Call(typeof(Enumerable), "Contains", new[] { typeof(string) }, someValue, propertyExp);
return Expression.Lambda<Func<T, bool>>(containsMethodExp, parameterExp);
}
在这种情况下,"Codigo"是硬编码的,但它可以是一个参数,用于获取您定义的类型的任何属性。
您可以使用进行测试
public void LambdaConversionBasicWithEmissor()
{
var cust= new Customer();
var items = new List<string>() { "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<object, bool>(i => new Contains(items, i).Value) });
}
}
最后一步只是调用我们从Where类获得的结果,该类实际上是Enumerable.WhereArrayTerator类型,而不是List类型,因为Where Extension方法的结果是延迟执行的结果。
因此,我们需要通过调用ToList扩展方法来创建一个非延迟对象,并获得结果。
class ToList
{
public List<object> 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<object>;
}
}
最后,您可以使用下面的类简单地测试整个过程。
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();
}
}