c# - 组合表达式 c#

标签 c# expression expression-trees

我需要连接两个表达式(使用 语句)

我的代码:

var items = new List<Item>
{
    new Item { Color = "Black", Categories = new List<string> { "cat1", "cat2" } },
    new Item { Color = "Red", Categories = new List<string> { "cat3" } },
    new Item { Color = "White", Categories = new List<string> { "cat1" } }
};

var categories = new List<string> { "cat2", "cat3" };

Expression<Func<Item, bool>> func1 = (x1) => x1.Color == "Black";
Expression<Func<Item, bool>> func2 = (x2) => x2.Categories.Any(y => categories.Where(z => z == y).Any());
Expression<Func<Item, bool>> fullExpression = Expression.Lambda<Func<Item, bool>>(
        Expression.Or(func1.Body, func2.Body), func1.Parameters.Single());

var result = items.AsQueryable().Where(fullExpression);
// result should be like this
// items.Where(x => (x.Color == "Black") || x.Categories.Any(y => categories.Where(z => z == y).Any()))

我收到运行时错误 从范围“”引用的类型为“项目”的变量“x2”,但未定义”

我也尝试用 ExpressionVisitor 构建一个表达式。

这是 ExpressionVisitor:

internal class ParameterReplacer : ExpressionVisitor
{
    private readonly ParameterExpression _parameter;

    internal ParameterReplacer(ParameterExpression parameter)
    {
        _parameter = parameter;
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        return base.VisitParameter(_parameter);
    }
}

这里是我在代码中使用它的方式:

Expression<Func<Item, bool>> func1 = (x1) => x1.Color == "Black";
Expression<Func<Item, bool>> func2 = (x2) => x2.Categories.Any(y => categories.Select(z => z == y).Any());
var paramExpr = Expression.Parameter(typeof(Item));
var exprBody = Expression.Or(func1.Body, func2.Body);
exprBody = (BinaryExpression)new ParameterReplacer(paramExpr).Visit(exprBody);
var finalExpr = Expression.Lambda<Func<Item, bool>>(exprBody, paramExpr);
var result = items.AsQueryable().Where(finalExpr);

在这种情况下,在创建 ParameterReplacer 时出现错误

System.InvalidOperationException: 'The operands for operator 'Equal' do not match the parameters of method 'op_Equality'.'

我做错了什么?

最佳答案

Ian Newson 是完全正确的,但如果你想要代码,那就来吧:)

使用这两个类,您可以组合两个谓词。它不是我想出来的,而是对其进行了一些改进/调整并使其使用类型 Predicate而不是 Func以及一些较新的语言功能(原来的很旧,遗憾的是我不记得我是在哪里找到它的)。

internal class SubstExpressionVisitor : ExpressionVisitor
{
    private readonly Dictionary<Expression, Expression> _subst = new Dictionary<Expression, Expression>();

    protected override Expression VisitParameter(ParameterExpression node)
    {
        if (_subst.TryGetValue(node, out Expression newValue))
        {
            return newValue;
        }

        return node;
    }

    public Expression this[Expression original]
    {
        get => _subst[original];
        set => _subst[original] = value;
    }
}
public static class PredicateBuilder
{
    // you don't seem to need this but it's included for completeness sake
    public static Expression<Predicate<T>> And<T>(this Expression<Predicate<T>> a, Expression<Predicate<T>> b)
    {
        if (a == null)
            throw new ArgumentNullException(nameof(a));

        if (b == null)
            throw new ArgumentNullException(nameof(b));

        ParameterExpression p = a.Parameters[0];

        SubstExpressionVisitor visitor = new SubstExpressionVisitor();
        visitor[b.Parameters[0]] = p;

        Expression body = Expression.AndAlso(a.Body, visitor.Visit(b.Body));
        return Expression.Lambda<Predicate<T>>(body, p);
    }

    public static Expression<Predicate<T>> Or<T>(this Expression<Predicate<T>> a, Expression<Predicate<T>> b)
    {
        if (a == null)
            throw new ArgumentNullException(nameof(a));

        if (b == null)
            throw new ArgumentNullException(nameof(b));

        ParameterExpression p = a.Parameters[0];

        SubstExpressionVisitor visitor = new SubstExpressionVisitor();
        visitor[b.Parameters[0]] = p;

        Expression body = Expression.OrElse(a.Body, visitor.Visit(b.Body));
        return Expression.Lambda<Predicate<T>>(body, p);
    }
}

你可以这样使用它:

Expression<Predicate<Item>> func1 = (x1) => x1.Color == "Black";
Expression<Predicate<Item>> func2 = (x2) => x2.Categories.Any(y => categories.Select(z => z == y).Any());

Expression<Predicate<Item>> finalExpr = func1.Or(func2);

您可能要记住我的 Or正在使用 OrElse在内部,如果前一个表达式被评估为真( OrElse 类似于 || ,而不是 | ),则不会评估第二个表达式。 And也是如此与 AndAlso ( AndAlso 类似于 && ,而不是 & )。
另一件需要注意的事情是,您可以轻松替换 Predicate<T>Func<T, bool>如果你必须使用 Func出于某种原因:)

关于c# - 组合表达式 c#,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58260977/

相关文章:

c# - LINQ to Entities 仅支持使用 IEntity 接口(interface)转换 EDM 原语或枚举类型

c# - 编译委托(delegate)表达式的性能

c# - 发出ajax请求时,如何在使用c#的url参数中使用jquery变量?

ios - 如何确定两个数学表达式 'sort of' 是否相同?

java - 需要有关带 namespace 的 xPath 表达式的帮助

c# - 如何将多个 Linq 表达式的结果组合成一个表达式?

c# - ThreadState 属性的值是多少?

c# - 如何将新任务添加到现有任务

c# - 绑定(bind)到另一个命名空间中的属性?

c# - 为什么 Expression.And 代表 "&"而不是 "&&"