296

タイプの式が 2 つExpression<Func<T, bool>>あり、これらの OR、AND、または NOT を取得して、同じタイプの新しい式を取得したい

Expression<Func<T, bool>> expr1;
Expression<Func<T, bool>> expr2;

...

//how to do this (the code below will obviously not work)
Expression<Func<T, bool>> andExpression = expr AND expr2
4

9 に答える 9

406

Expression.AndAlso/ OrElseetc を使用して論理式を組み合わせることができますが、問題はパラメーターです。ParameterExpressionexpr1 と expr2 で同じ作業をしていますか? もしそうなら、それはより簡単です:

var body = Expression.AndAlso(expr1.Body, expr2.Body);
var lambda = Expression.Lambda<Func<T,bool>>(body, expr1.Parameters[0]);

これは、単一の操作を否定するためにもうまく機能します。

static Expression<Func<T, bool>> Not<T>(
    this Expression<Func<T, bool>> expr)
{
    return Expression.Lambda<Func<T, bool>>(
        Expression.Not(expr.Body), expr.Parameters[0]);
}

それ以外の場合、LINQ プロバイダーによっては、次のように組み合わせることができる場合がありますInvoke

// OrElse is very similar...
static Expression<Func<T, bool>> AndAlso<T>(
    this Expression<Func<T, bool>> left,
    Expression<Func<T, bool>> right)
{
    var param = Expression.Parameter(typeof(T), "x");
    var body = Expression.AndAlso(
            Expression.Invoke(left, param),
            Expression.Invoke(right, param)
        );
    var lambda = Expression.Lambda<Func<T, bool>>(body, param);
    return lambda;
}

どこかで、ノードを置き換える式ツリーを書き直しInvokeて .


最も単純なルートを選択する一般化されたバージョン:

static Expression<Func<T, bool>> AndAlso<T>(
    this Expression<Func<T, bool>> expr1,
    Expression<Func<T, bool>> expr2)
{
    // need to detect whether they use the same
    // parameter instance; if not, they need fixing
    ParameterExpression param = expr1.Parameters[0];
    if (ReferenceEquals(param, expr2.Parameters[0]))
    {
        // simple version
        return Expression.Lambda<Func<T, bool>>(
            Expression.AndAlso(expr1.Body, expr2.Body), param);
    }
    // otherwise, keep expr1 "as is" and invoke expr2
    return Expression.Lambda<Func<T, bool>>(
        Expression.AndAlso(
            expr1.Body,
            Expression.Invoke(expr2, param)), param);
}

.NET 4.0 以降では、ExpressionVisitorEF セーフな式を作成できるクラスがあります。

    public static Expression<Func<T, bool>> AndAlso<T>(
        this Expression<Func<T, bool>> expr1,
        Expression<Func<T, bool>> expr2)
    {
        var parameter = Expression.Parameter(typeof (T));

        var leftVisitor = new ReplaceExpressionVisitor(expr1.Parameters[0], parameter);
        var left = leftVisitor.Visit(expr1.Body);

        var rightVisitor = new ReplaceExpressionVisitor(expr2.Parameters[0], parameter);
        var right = rightVisitor.Visit(expr2.Body);

        return Expression.Lambda<Func<T, bool>>(
            Expression.AndAlso(left, right), parameter);
    }



    private class ReplaceExpressionVisitor
        : ExpressionVisitor
    {
        private readonly Expression _oldValue;
        private readonly Expression _newValue;

        public ReplaceExpressionVisitor(Expression oldValue, Expression newValue)
        {
            _oldValue = oldValue;
            _newValue = newValue;
        }

        public override Expression Visit(Expression node)
        {
            if (node == _oldValue)
                return _newValue;
            return base.Visit(node);
        }
    }
于 2009-01-19T11:32:55.960 に答える
81

Expression.AndAlso / OrElse を使用して論理式を組み合わせることができますが、ParameterExpressions が同じであることを確認する必要があります。

私はEFとPredicateBuilderに問題があったので、Invokeに頼らずに自分で作成しました。これは次のように使用できます。

var filterC = filterA.And(filterb);

私の PredicateBuilder のソース コード:

public static class PredicateBuilder {

    public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> a, Expression<Func<T, bool>> b) {    

        ParameterExpression p = a.Parameters[0];

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

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

    public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> a, Expression<Func<T, bool>> b) {    

        ParameterExpression p = a.Parameters[0];

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

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

そして、ラムダのパラメータを置き換えるユーティリティ クラス:

internal class SubstExpressionVisitor : System.Linq.Expressions.ExpressionVisitor {
        public Dictionary<Expression, Expression> subst = new Dictionary<Expression, Expression>();

        protected override Expression VisitParameter(ParameterExpression node) {
            Expression newValue;
            if (subst.TryGetValue(node, out newValue)) {
                return newValue;
            }
            return node;
        }
    }
于 2012-09-19T14:53:17.223 に答える
4

同じ結果を得る必要がありましたが、より一般的なものを使用しました (型が不明だったため)。マークの答えのおかげで、私は最終的に私が達成しようとしていたことを理解しました:

    public static LambdaExpression CombineOr(Type sourceType, LambdaExpression exp, LambdaExpression newExp) 
    {
        var parameter = Expression.Parameter(sourceType);

        var leftVisitor = new ReplaceExpressionVisitor(exp.Parameters[0], parameter);
        var left = leftVisitor.Visit(exp.Body);

        var rightVisitor = new ReplaceExpressionVisitor(newExp.Parameters[0], parameter);
        var right = rightVisitor.Visit(newExp.Body);

        var delegateType = typeof(Func<,>).MakeGenericType(sourceType, typeof(bool));
        return Expression.Lambda(delegateType, Expression.Or(left, right), parameter);
    }
于 2017-06-01T12:48:34.803 に答える
-9

これはうまくいくと思いますね。

Func<T, bool> expr1 = (x => x.Att1 == "a");
Func<T, bool> expr2 = (x => x.Att2 == "b");
Func<T, bool> expr1ANDexpr2 = (x => expr1(x) && expr2(x));
Func<T, bool> expr1ORexpr2 = (x => expr1(x) || expr2(x));
Func<T, bool> NOTexpr1 = (x => !expr1(x));
于 2015-11-13T15:11:18.143 に答える