[英]Expression Tree with chained string methods
我創建了一個看起來像這樣的謂詞:
p.Name.Contains("Saw")
我有以下代碼:
private static Expression<Func<T, bool>> BuildContainsPredicate<T>(string propertyName, string propertyValue)
{
PropertyInfo propertyInfo = typeof (T).GetProperty(propertyName);
// ListOfProducts.Where(p => p.Contains(propertyValue))
ParameterExpression pe = Expression.Parameter(typeof(T), "p");
MemberExpression memberExpression = Expression.MakeMemberAccess(pe, propertyInfo);
MethodInfo methodInfo = typeof (string).GetMethod("Contains", new Type[] {typeof (string)});
ConstantExpression constantExpression = Expression.Constant(propertyValue, typeof(string));
// Predicate Body - p.Name.Contains("Saw")
Expression call = Expression.Call(memberExpression, methodInfo, constantExpression);
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(call, pe);
return lambda;
}
但我想將謂詞更改為:
p.Name.ToLower().Contains("Saw")
我空白了。 我知道我必須在定義MethodInfo的地方添加一些東西。
有沒有人有建議?
而不是手動構造整個表達式只是因為一個小小塊是動態的,你可以使用常規lambda來定義所有實際上是靜態的內容,然后只需替換那些不是的小塊。
具體來說,您可以使用的一般策略是使用一個帶有表示您的小動態位的參數的lambda,然后在常規lambda中使用它,然后用動態構造的表達式替換該參數的所有實例:
private static Expression<Func<T, bool>> BuildContainsPredicate<T>(
string propertyName, string propertyValue)
{
Expression<Func<string, bool>> e = s => s.ToLower().Contains(propertyValue);
var parameter = Expression.Parameter(typeof(T));
var property = Expression.PropertyOrField(parameter, propertyName);
var body = e.Body.Replace(e.Parameters[0], property);
return Expression.Lambda<Func<T, bool>>(body, parameter);
}
這不僅簡化了您的原始代碼,而且對此靜態代碼進行其他更改就像編輯任何常規的舊C#代碼一樣簡單,而不是要求所有的表達式操作,以及隨之而來的復雜性(以及靜態類型的丟失) 。
此解決方案使用以下方法將一個表達式的所有實例替換為另一個:
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
{
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
internal class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
另一種讓事情變得更高級的方法是編寫一個Compose
方法,讓你輕松地Compose
表達式。 從概念上講,我們將有兩個lambda,我們想要創建一個lambda來表示調用一個並將其結果傳遞給另一個,然后返回結果:
public static Expression<Func<TFirstParam, TResult>>
Compose<TFirstParam, TIntermediate, TResult>(
this Expression<Func<TFirstParam, TIntermediate>> first,
Expression<Func<TIntermediate, TResult>> second)
{
var param = Expression.Parameter(typeof(TFirstParam), "param");
var newFirst = first.Body.Replace(first.Parameters[0], param);
var newSecond = second.Body.Replace(second.Parameters[0], newFirst);
return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}
這或多或少使用了我們上面使用的相同策略,但它概括了它而不是特殊的表達式。
然后,我們在將各塊放在一起之前有一個剩余的輔助方法; 創建一個表示訪問屬性的方法的方法,該屬性由屬性的字符串名稱定義:
public static Expression<Func<T, string>> MemberSelector<T>(string propertyName)
{
var param = Expression.Parameter(typeof(T));
var body = Expression.PropertyOrField(param, propertyName);
return Expression.Lambda<Func<T, string>>(body, param);
}
使用這兩個輔助方法(不依賴於任何特定情況),我們現在可以構建我們想要的lambda 而無需任何自定義構建的表達式操作 :
private static Expression<Func<T, bool>> BuildContainsPredicate<T>(
string propertyName, string propertyValue)
{
return MemberSelector<T>(propertyName)
.Compose(prop => prop.ToLower().Contains(propertyValue));
}
您必須獲取ToLower
方法的表達式,然后在Contains
表達式中使用它
private static Expression<Func<T, bool>> BuildContainsPredicate<T>(string propertyName, string propertyValue)
{
PropertyInfo propertyInfo = typeof (T).GetProperty(propertyName);
ParameterExpression pe = Expression.Parameter(typeof(T), "p");
MemberExpression memberExpression = Expression.MakeMemberAccess(pe, propertyInfo);
//ToLower expression
MethodInfo toLowerMethodInfo = typeof (string).GetMethod("ToLower", new Type[]{});
Expression toLowerCall = Expression.Call(memberExpression, toLowerMethodInfo);
MethodInfo containsMethodInfo = typeof (string).GetMethod("Contains", new Type[] {typeof (string)});
ConstantExpression constantExpression = Expression.Constant(propertyValue, typeof(string));
// Pass ToLowerCall to
Expression call = Expression.Call(toLowerCall, containsMethodInfo, constantExpression);
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(call, pe);
return lambda;
}
關於安德烈的答案,這基本上是我在Servy的初步評論之后提出的,我想發布我想出的內容:
private static Expression<Func<T, bool>> BuildContainsPredicate<T>(string propertyName, string propertyValue)
{
PropertyInfo propertyInfo = typeof (T).GetProperty(propertyName);
// ListOfProducts.Where(p => p.Contains(propertyValue))
ParameterExpression pe = Expression.Parameter(typeof(T), "p");
MemberExpression memberExpression = Expression.MakeMemberAccess(pe, propertyInfo);
// Thanks to Servy's suggestion
Expression toLowerExpression = Expression.Call(memberExpression, typeof(string).GetMethod("ToLower", Type.EmptyTypes));
MethodInfo methodInfo = typeof (string).GetMethod("Contains", new Type[] {typeof (string)});
ConstantExpression constantExpression = Expression.Constant(propertyValue, typeof(string));
// Predicate Body - p.Name.Contains("Saw")
Expression call = Expression.Call(toLowerExpression, methodInfo, constantExpression);
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(call, pe);
return lambda;
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.