سؤال

ولدي تعبيرين من نوع 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
هل كانت مفيدة؟

المحلول

حسنا، يمكنك استخدام Expression.AndAlso / OrElse غيرها من الجمع بين التعابير المنطقية، ولكن المشكلة هي المعلمات. أنت تعمل مع نفسه ParameterExpression في expr1 و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);
}

وانطلاقا من صافي 4.0. وهناك فئة ExpressionVistor الذي يسمح لك لبناء التعبيرات التي هي EF آمنة.

    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);
        }
    }

نصائح أخرى

ويمكنك استخدام Expression.AndAlso / OrElse إلى الجمع بين التعابير المنطقية، ولكن لديك للتأكد من أن ParameterExpressions هي نفسها.

وكنت تواجه مشكلة مع EF و PredicateBuilder ذلك جعل بلدي دون اللجوء إلى استدعاء، وأنني يمكن استخدام مثل هذا:

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;
        }
    }

وجو Albahari (مؤلف C # 3.0 في القشر وLINQPad) كتب مساعدة تسمى PredicateBuilder التي يمكن استخدامها لAND و OR وظائف معا.

http://www.albahari.com/nutshell/predicatebuilder.aspx

وبينما كان يعمل على وظائف فهو مفتوح المصدر بحيث يمكنك التحقق من ذلك ونرى كيف يعمل.

إذا كنت لا مزود دعم استدعاء وتحتاج إلى الجمع بين اثنين التعبير، يمكنك استخدام ExpressionVisitor لاستبدال المعلمة في التعبير الثاني بواسطة المعلمة في التعبير الأول.

class ParameterUpdateVisitor : ExpressionVisitor
{
    private ParameterExpression _oldParameter;
    private ParameterExpression _newParameter;

    public ParameterUpdateVisitor(ParameterExpression oldParameter, ParameterExpression newParameter)
    {
        _oldParameter = oldParameter;
        _newParameter = newParameter;
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        if (object.ReferenceEquals(node, _oldParameter))
            return _newParameter;

        return base.VisitParameter(node);
    }
}

static Expression<Func<T, bool>> UpdateParameter<T>(
    Expression<Func<T, bool>> expr,
    ParameterExpression newParameter)
{
    var visitor = new ParameterUpdateVisitor(expr.Parameters[0], newParameter);
    var body = visitor.Visit(expr.Body);

    return Expression.Lambda<Func<T, bool>>(body, newParameter);
}

[TestMethod]
public void ExpressionText()
{
    string text = "test";

    Expression<Func<Coco, bool>> expr1 = p => p.Item1.Contains(text);
    Expression<Func<Coco, bool>> expr2 = q => q.Item2.Contains(text);
    Expression<Func<Coco, bool>> expr3 = UpdateParameter(expr2, expr1.Parameters[0]);

    var expr4 = Expression.Lambda<Func<Recording, bool>>(
        Expression.OrElse(expr1.Body, expr3.Body), expr1.Parameters[0]);

    var func = expr4.Compile();

    Assert.IsTrue(func(new Coco { Item1 = "caca", Item2 = "test pipi" }));
}

وأقترح أحد تحسن أكثر لحلول <م> PredicateBuilder وExpressionVisitor. أسميتها UnifyParametersByName ويمكنك العثور عليها في مكتبة معهد ماساتشوستس للتكنولوجيا من الألغام: LinqExprHelper . انها تسمح للجمع بين تعبيرات لامدا التعسفي. وعادة ما يتم طرح الأسئلة حول التعبير المسند، ولكن هذه الفكرة يمتد إلى تعبيرات الإسقاط كذلك.

والتعليمة البرمجية التالية توظف ExprAdres الطريقة التي يخلق التعبير parametrized معقدة، وذلك باستخدام مضمنة امدا. يتم ترميز هذا التعبير معقد مرة واحدة فقط، ومن ثم إعادة استخدامها، وذلك بفضل LinqExprHelper المصغرة المكتبة.

public IQueryable<UbezpExt> UbezpFull
{
    get
    {
        System.Linq.Expressions.Expression<
            Func<UBEZPIECZONY, UBEZP_ADRES, UBEZP_ADRES, UbezpExt>> expr =
            (u, parAdrM, parAdrZ) => new UbezpExt
            {
                Ub = u,
                AdrM = parAdrM,
                AdrZ = parAdrZ,
            };

        // From here an expression builder ExprAdres is called.
        var expr2 = expr
            .ReplacePar("parAdrM", ExprAdres("M").Body)
            .ReplacePar("parAdrZ", ExprAdres("Z").Body);
        return UBEZPIECZONY.Select((Expression<Func<UBEZPIECZONY, UbezpExt>>)expr2);
    }
}

وهذا هو قانون البناء التعبير الجزئي:

public static Expression<Func<UBEZPIECZONY, UBEZP_ADRES>> ExprAdres(string sTyp)
{
    return u => u.UBEZP_ADRES.Where(a => a.TYP_ADRESU == sTyp)
        .OrderByDescending(a => a.DATAOD).FirstOrDefault();
}

ما حاولت تحقيق وتنفيذ استعلامات parametrized دون الحاجة إلى نسخ ولصق ومع القدرة على استخدام lambdas المضمنة، التي هي جميلة جدا. دون كل هذه المساعد التعبير عن الأشياء، وأنا سوف تضطر إلى إنشاء الاستعلام كله دفعة واحدة.

وأنا في حاجة إلى تحقيق نفس النتائج، ولكن باستخدام شيء أكثر عمومية (كما لم يعرف نوع). بفضل الجواب مارك وأنا برزت أخيرا ما كنت أحاول أن تحقيق ما يلي:

    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);
    }

وأعتقد أن هذا يعمل بشكل جيد، أليس كذلك؟

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));
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top