c# - 具有链式字符串方法的表达式树

标签 c# lambda

我创建了一个如下所示的谓词:

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 方法,让您可以轻松地组合表达式。从概念上讲,我们将有两个 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));
}

关于c# - 具有链式字符串方法的表达式树,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30265488/

相关文章:

c# - async-await 如何不阻塞?

c# - 将 CASE 从 VB 转移到 C# 中的 SWITCH

c# - 如何在 View 之间传递数据

c# - 如何根据当前项目构建调用另一个依赖项函数的依赖项?

Ruby:lambda 函数参数可以有默认值吗?

c# - 从列表 1 中删除不在列表 2 中的项目

c# - Web 和 Winforms 的 .Net 身份验证

java - 我应该在 TDD 中使用 lambda 吗?

java - Log4j2 在方法引用调用中打印实用程序类行号

c++ - std::future 如何影响关联的 std::packaged_task 的生命周期?