簡體   English   中英

帶有鏈式字符串方法的表達式樹

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM