对于
IQueryable
,您需要为筛选谓词创建一个
LambdaExpression
。(对于
IEnumerable
,您可以将该表达式编译成适当的
Func<>
。)
所有这些都通过构建表示所需操作的表达式树来实现。在本例中,您正在调用
Contains
,传递过滤值的常量给获取属性值的结果。
让我们从
Contains
方法开始,您可以重复使用它。不是基本反射,而是以下是如何使用表达式获取它:
static readonly MethodInfo _contains =
(((Expression<Func<string, bool>>)(s => s.Contains("a"))).Body as MethodCallExpression)
.Method;
虽然这看起来有点令人困惑,但我们正在利用编译器来为我们完成反射工作。有时这比查找具有多个重载版本的方法的正确版本或者当不清楚涉及哪个扩展方法时更容易。结果是在这里_contains
被初始化为我们需要的方法信息。
您已经拥有目标属性的属性信息,现在让我们将它们组合起来:
var row = Expression.Parameter(typeof(T), "row");
var filter = Expression.Constant(filterValue);
var prop = Expression.Property(property);
var body = Expression.Call(prop, _contains, filter);
var predicate = Expression.Lambda<Func<T, bool>(body, row);
query = query.Where(predicate);
或者以更紧凑的形式表达:
var row = Expression.Parameter(typeof(T), "row");
var predicate =
Expression.Lambda<Func<T, bool>
(
Expression.Call
(
Expression.Property(row, property),
_contains,
Expression.Constant(filterValue)
),
row
);
当你使用 IEnumerable<T>
处理数据时,predicate.Compile()
会生成一个可用的 Func<T, bool>
,以传递给 IEnumerable.Where()
:
private static readonly MethodInfo _tostring = typeof(Object).GetMethod("ToString");
static readonly MethodInfo _compare = (((Expression<Func<string, bool>>)(s => s.Contains(""))).Body as MethodCallExpression).Method;
public static IEnumerable<T> Search<T>(this IEnumerable<T> items, string propertyName, string filterValue)
{
var property = typeof(T).GetProperty(propertyName);
var row = Expression.Parameter(typeof(T), "row");
Expression prop = Expression.Property(row, property);
if (property.PropertyType != typeof(string))
prop = Expression.Call(prop, _tostring);
var func =
Expression.Lambda<Func<T, bool>>
(
Expression.Call
(
prop,
_compare,
Expression.Constant(filterValue)
),
row
).Dump().Compile();
return items.Where(func);
}
表达式非常灵活,有很多场合可以派上用场。组合函数并多次调用它可能比一直进行反射更有效率,还可以通过合并表达式等方式实现有趣的功能。
MethodCallExpression
和MemberExpression
都是从Expression
派生出来的,所以显式地为变量指定类型是可以的。 - Corey