Объединение выражения с оператором && - PullRequest
4 голосов
/ 23 ноября 2011

У меня есть класс, который работает немного как предложение Linq To Sql Where.

Он строит последовательность операций из дерева выражений.

Дерево выражений представляет собой Expression<Func<bool>> (т.е. лямбда без аргументов, которая возвращает логическое значение)

conditionBuilder.BuildCondition(() => x != 3 && y != 5);

Класс отлично работает для normal выражений, как в примере выше, но теперь мне нужна функциональность для объединения выражений.

Я добавил And, Или такие методы, как

var exp1 = () => x != 3;
var exp2 = () => y != 5;
var exp = ConditionBuilder.And(exp1, exp2);

но это усложняется при объединении нескольких выражений.

Я хотел бы написать

var exp = exp1 && exp2;

но поскольку я не могу напрямую перегрузить оператор &&, мне нужно найти другое решение. Сложность в том, что в результирующих операциях нет логической перегрузки для побитовых операторов. то есть результат exp1 и exp2 - это int, а не bool. (Я могу обойти это, добавив != 0)

Итак, мои вопросы сейчас:

  • Будет ли сбивающим с толку, если я позволю оператору & быть логическим выражением (т.е. AndAlso)?
  • Оператор && будет работать, если я перегружу & / true / false, но это также создаст неявное логическое преобразование. Я знаю, что неявное логическое преобразование - это то, чего вы хотите избежать в C ++, но я не уверен, насколько это важно в C #. Кроме того, должны ли переопределенные истина и ложь исказить выражение? (то есть что if (exp1) должен делать?)

Edit: У меня уже есть рабочий код, подобный этому:

public class ConditionBuilder
{
    private readonly Expression<Func<bool>> _filter;

    public ConditionBuilder(Expression<Func<bool>> filter) {
        _filter = filter;
    }

    public static ConditionBuilder And(ConditionBuilder left, ConditionBuilder right) {
        return new ConditionBuilder(Expression.Lambda<Func<bool>>(Expression.AndAlso(left._filter.Body, right._filter.Body)));
    }

    public static ConditionBuilder Or(ConditionBuilder left, ConditionBuilder right) {
        return new ConditionBuilder(Expression.Lambda<Func<bool>>(Expression.OrElse(left._filter.Body, right._filter.Body)));
    }
}

Редактировать 2 уточнить вопросы.

Выражения преобразуются в другой формат. Например, () => ConditionBuilder.IntField(123) == 5 конвертируется в @123 EQ 5 (реальный формат - это нечто иное, но вы поняли)

Проблема в том, что другой формат не имеет логической перегрузки для побитовых операторов. Это означает, что () => true & false преобразуется в True BITAND False, которое не является допустимым выражением, поскольку возвращает целое число, а не логическое значение.

Если я перегрузить & иметь в виду AndAlso

exp1 & exp2

является допустимым выражением, но

() => x != 3 & y != 5

нет.

Мой второй вопрос был, если неявное преобразование в bool вызывает проблемы в C #, как это происходит в C ++.

1 Ответ

3 голосов
/ 23 ноября 2011

Я бы перегрузил оператор &.Это не сбивает с толку, поскольку & может быть логическим или побитовым в зависимости от контекста.

Чтобы перегрузить оператор &, вам придется использовать класс-оболочку (например, ConditionBuilder) для перегруженных операторов.

Вы можете увидеть полный пример перегрузки различных операторов, включая & в документации для оператора true, http://msdn.microsoft.com/en-us/library/6x6y6z4d.aspx.


Простой пример использования ConditionBuilder для демонстрации перегрузкиоператор &).

void Main ()
{
    int x = 1;
    int y = 1;

    var exp1 = new ConditionBuilder (() => x != 3);
    var exp2 = new ConditionBuilder (() => y != 5);
    var exp3 = exp1 & exp2;

    Console.WriteLine (exp3.Execute ());
}

public class ConditionBuilder
{
    private readonly Expression<Func<bool>> _filter;

    public ConditionBuilder(Expression<Func<bool>> filter) {
        _filter = filter;
    }

    public bool Execute() {
        return _filter.Compile()();
    }

    public static ConditionBuilder And(ConditionBuilder left, ConditionBuilder right) {
        return new ConditionBuilder(Expression.Lambda<Func<bool>>(Expression.AndAlso(left._filter.Body, right._filter.Body)));
    }

    public static ConditionBuilder Or(ConditionBuilder left, ConditionBuilder right) {
        return new ConditionBuilder(Expression.Lambda<Func<bool>>(Expression.OrElse(left._filter.Body, right._filter.Body)));
    }

    public static ConditionBuilder operator & (ConditionBuilder left, ConditionBuilder right) {
        // Note this could confuse users for the problem discussed below.
        // Consider using Expression.And instead of AndAlso
        return ConditionBuilder.And(left, right);
    }

    public static ConditionBuilder operator | (ConditionBuilder left, ConditionBuilder right) {
        // Note this could confuse users for the problem discussed below.
        // Consider using Expression.Or instead of OrElse
        return ConditionBuilder.Or(left, right);
    }
}

Я был бы осторожен, используя операторы короткого замыкания (AndAlso, OrElse) с & и |, как указано Foo() & Bar(), когда Fooвозвращает false Бар будет вызываться не , что будет неожиданно для большинства пользователей.


Альтернатива

Измените методы ConditionBuilder And / Or на элементы-экземпляры и принимайте толькоправая сторона в качестве аргумента.

public ConditionBuilder And(ConditionBuilder right) {
    return new ConditionBuilder(Expression.Lambda<Func<bool>>(Expression.AndAlso(_filter.Body, right._filter.Body)));
}

Это дает такой синтаксис, как var exp3 = exp1.And(exp2);, который точнее оригинала и будет достаточно хорошо сцепляться exp1.And(exp2).And(exp3).

У вас будетбыть осторожным при смешивании и / или, поскольку exp1.Or(exp2).And(exp3) даст вам (exp1 | exp2) & exp3, а не exp1 | (exp2 & exp3).Этого можно избежать, используя явные скобки, такие как (exp1.Or(exp2)).And(exp3) против exp1.Or(exp2.And(exp3))


Проблема с поддержкой &&

Для поддержки && вам потребуется поддержка true иfalse операторы в ConditionBuilder, что будет означать компиляцию и выполнение выражения.

public static bool operator true (ConditionBuilder left) {
    return left.Execute();
}

public static bool operator false (ConditionBuilder left) {
    return !left.Execute();
}

При тестировании этого с LinqPAD результат из var exp3 = exp1 && exp2 имел результаты ниже идеальных.

  1. Для устранения короткого замыкания необходимо выполнить exp1 немедленно.
  2. Когда exp1 было истинным, результирующее выражение было эквивалентно:
    var exp3 = () => x != 3 && y != 5
  3. Когда exp1 было ложным, результирующее выражение былоэквивалентно:
    var exp3 = () => x != 3
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...