Исключение с использованием методов выражения OrElse и AndAlso

Я пытаюсь построить дерево выражений программно.

У меня есть на входе список классов условий, которые имеют следующую форму:

public class Filter
{
    public string field { get; set; }
    public string operator { get; set; }
    public string value { get; set; }
}

Когда я создаю объект Expression, я создаю Expression для каждого условия следующим образом.

foreach ( Filter sf in rules ) {
    Expression ex = sf.ToExpression( query );
    if ( mainExpression == null ) {
        mainExpression = ex;
    }
    else {
        if ( logicalCondition == "AND" ) {
            mainExpression = Expression.And( mainExpression, ex );
        }
        else if ( logicalCondition == "OR" ) {
            mainExpression = Expression.Or( mainExpression, ex );
        }
    }
}

Метод Filter.ToExpression() реализован следующим образом

public override Expression ToExpression( IQueryable query ) {
    ParameterExpression parameter = Expression.Parameter( query.ElementType, "p" );
    MemberExpression memberAccess = null;
    foreach ( var property in field.Split( '.' ) )
        memberAccess = MemberExpression.Property( memberAccess ?? ( parameter as Expression ), property );
    ConstantExpression filter = Expression.Constant( Convert.ChangeType( value, memberAccess.Type ) );
    WhereOperation condition = (WhereOperation)StringEnum.Parse( typeof( WhereOperation ), operator );
    LambdaExpression lambda = BuildLambdaExpression( memberAccess, filter, parameter, condition, value );
    return lambda;
}

Все работает, когда у меня есть одно условие, но когда я пытаюсь объединить выражения, используя один из статических методов And, Or, AndAlso, OrElse, я получаю InvalidOperationException, который говорит:

Бинарный оператор Or не определен для типов «System.Func2[MyObject,System.Boolean]' and 'System.Func2[MyObject,System.Boolean]».

Я немного запутался. Может ли кто-нибудь лучше объяснить причины исключения и предложить решение?

Большое спасибо!


person Lorenzo    schedule 10.02.2012    source источник


Ответы (1)


Вы объединяете a => a == 3 и a => a == 4 в (a => a == 3) || (a => a == 4), но вместо этого вы должны попытаться сделать его a => (a == 3 || a == 4). Это несложно сделать вручную, но кто-то уже сделал это за вас. Найдите «Объединение выражений».

Редактировать: по просьбе, простой пример того, как сделать это вручную.

Изменить 2: используется ExpressionVisitor, который является новым для .NET 4, но в MSDN вы можете найти полезную реализацию для более ранних версий. Я предполагаю, что код MSDN не квалифицируется как «сторонний» для ваших целей. Вам нужно только изменить метод protected virtual Expression Visit(Expression exp) на public. А поскольку Enumerable.Zip для вас недоступен и не нужен, его сейчас нет.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;

namespace DemoApp
{
    <include ExpressionVisitor definition here for .NET 3.5>

    public class ExpressionParameterReplacer : ExpressionVisitor
    {
        public ExpressionParameterReplacer(IList<ParameterExpression> fromParameters, IList<ParameterExpression> toParameters)
        {
            ParameterReplacements = new Dictionary<ParameterExpression, ParameterExpression>();
            for (int i = 0; i != fromParameters.Count && i != toParameters.Count; i++)
                ParameterReplacements.Add(fromParameters[i], toParameters[i]);
        }
        private IDictionary<ParameterExpression, ParameterExpression> ParameterReplacements
        {
            get;
            set;
        }
        protected override Expression VisitParameter(ParameterExpression node)
        {
            ParameterExpression replacement;
            if (ParameterReplacements.TryGetValue(node, out replacement))
                node = replacement;
            return base.VisitParameter(node);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Expression<Func<int, bool>> exprA = a => a == 3;
            Expression<Func<int, bool>> exprB = b => b == 4;
            Expression<Func<int, bool>> exprC =
                Expression.Lambda<Func<int, bool>>(
                    Expression.OrElse(
                        exprA.Body,
                        new ExpressionParameterReplacer(exprB.Parameters, exprA.Parameters).Visit(exprB.Body)),
                    exprA.Parameters);
            Console.WriteLine(exprA.ToString());
            Console.WriteLine(exprB.ToString());
            Console.WriteLine(exprC.ToString());
            Func<int, bool> funcA = exprA.Compile();
            Func<int, bool> funcB = exprB.Compile();
            Func<int, bool> funcC = exprC.Compile();
            Debug.Assert(funcA(3) && !funcA(4) && !funcA(5));
            Debug.Assert(!funcB(3) && funcB(4) && !funcB(5));
            Debug.Assert(funcC(3) && funcC(4) && !funcC(5));
        }
    }
}
person Community    schedule 10.02.2012
comment
Привет, спасибо за ваш ответ. Я не могу использовать сторонний код для решения этой проблемы. Не могли бы вы лучше объяснить, как это сделать вручную? Спасибо еще раз! - person Lorenzo; 10.02.2012
comment
@Lorenzo Конечно, я добавил программу на основе двух выражений, которые я использовал в качестве примера. - person ; 10.02.2012
comment
Привет, я попытался реализовать ваше решение. Я понял, что ExpressionVisitor исходит из исходников LinqKit, и смог увидеть, как это работает. Теперь вопрос: откуда Zip метод IEnumerable<ParameterExpression>? Я использую .NET 3.5 и не могу найти этот метод :( - person Lorenzo; 11.02.2012
comment
@Lorenzo А, я использовал .NET 4, который поставляется как с классом ExpressionVisitor, так и с методом расширения Enumerable.Zip. Я посмотрю, смогу ли я заставить что-то работать и для 3.5, но это будет немного сложнее. - person ; 11.02.2012
comment
@Lorenzo Как оказалось, MS разместила функциональный ExpressionVisitor в MSDN, поэтому я проверил, работает ли он (работает), и упомянул об этом. А Enumerable.Zip на самом деле не нужен, поэтому я отказался от него и вместо него добавил простой цикл for. - person ; 11.02.2012
comment
Точно! И я жаловался, почему он просто не выполняет лямбда-выражения, которые возвращают bool, а затем ИЛИ их :) Все, что мне нужно было, это немного реструктурировать мой код, чтобы я генерировал логическое выражение со всеми ИЛИ, а затем помещал это в лямбду! - person Mzn; 27.03.2014