Конкатенация лямбда-функций в C # - PullRequest
27 голосов
/ 29 января 2009

Используя C # 3.5, я хотел создать предикат для отправки к предложению where по частям. Я создал очень простое консольное приложение, чтобы проиллюстрировать решение, к которому я пришел. Это работает отлично. Абсолютно отлично. Но я понятия не имею, как и почему.

    public static Func<Tran, bool> GetPredicate()
    {
        Func<Tran, bool> predicate = null;
        predicate += t => t.Response == "00";
        predicate += t => t.Amount < 100;
        return predicate;
    }

Когда я говорю «предикат + =», что это значит? предикат - = ничего не делает, а ^ =, & =, * =, / = не понравился компилятору.

Компилятору также не нравятся «предикат = предикат + t => t.Response ....».

На что я наткнулся? Я знаю, что он делает, но как он это делает?

Если кто-то хочет углубиться в более сложные лямбды, сделайте это.

Ответы [ 6 ]

32 голосов
/ 29 января 2009

«делегат + = метод» - оператор многоадресного делегата для объединения метода с делегатом. С другой стороны, «делегат - = метод» является оператором для удаления метода из делегата. Это полезно для действий.

Action action = Method1;
action += Method2;
action += Method3;
action -= Method2;
action();

В этом случае будут выполняться только Method1 и Method3, Method2 не будет работать, поскольку вы удаляете метод перед вызовом делегата.

Если вы используете многоадресный делегат с Func, результатом будет последний метод.

Func<int> func = () => 1;
func += () => 2;
func += () => 3;
int result = func();

В этом случае результат будет равен 3, поскольку метод "() => 3" является последним методом, добавленным к делегату. В любом случае будет вызван весь метод.

В вашем случае будет действовать метод "t => t.Amount <100". </p>

Если вы хотите объединить предикат, я предлагаю следующие методы расширения.

public static Func<T, bool> AndAlso<T>(
    this Func<T, bool> predicate1, 
    Func<T, bool> predicate2) 
{
    return arg => predicate1(arg) && predicate2(arg);
}

public static Func<T, bool> OrElse<T>(
    this Func<T, bool> predicate1, 
    Func<T, bool> predicate2) 
{
    return arg => predicate1(arg) || predicate2(arg);
}

Использование

public static Func<Tran, bool> GetPredicate() {
    Func<Tran, bool> predicate = null;
    predicate = t => t.Response == "00";
    predicate = predicate.AndAlso(t => t.Amount < 100);
    return predicate;
}

РЕДАКТИРОВАТЬ: исправить имя метода расширения, как предлагает Кит.

20 голосов
/ 29 января 2009

Когда вы используете + = или - = при типах делегата, это просто вызывает Delegate.Combine и Delegate.Remove.

Важная вещь о многоадресных делегатах заключается в том, что возвращаемое значение для всех, кроме последнего выполненного делегата , игнорируется. Все они выполняются (если не генерируется исключение), но используется только последнее возвращаемое значение.

Для предикатов вы можете сделать что-то вроде:

public static Func<T, bool> And<T>(params Func<T, bool>[] predicates)
{
    return t => predicates.All(predicate => predicate(t));
}

public static Func<T, bool> Or<T>(params Func<T, bool>[] predicates)
{
    return t => predicates.Any(predicate => predicate(t));
}

Вы бы тогда сделали:

Func<string, bool> predicate = And<string>(
    t => t.Length > 10,
    t => t.Length < 20);

РЕДАКТИРОВАТЬ: Вот более общее решение, которое довольно забавно, если немного странно ...

public static Func<TInput, TOuput> Combine<TInput, TOutput>
    (Func<TOutput, TOutput, TOutput> aggregator,
     params Func<TInput, TOuput>[] delegates) {

    // delegates[0] provides the initial value
    return t => delegates.Skip(1).Aggregate(delegates[0](t), aggregator);
}

Так вы могли бы затем реализовать А как:

public static Func<T, bool> And<T>(params Func<T, bool>[] predicates) {
    return Combine<T, bool>((x, y) => x && y, predicates);
}

(лично я предпочитаю это использовать GetInvocationList(), потому что в итоге вы получите предикат, который вы можете передать другим битам LINQ и т. Д.)

13 голосов
/ 29 января 2009

На самом деле, это не работает. Попробуйте проверить это в случае, когда первое условие не выполняется, а второе проходит. Вы обнаружите, что это вернет истину. Причина в том, что при работе с многоадресными делегатами, которые возвращают значения, возвращается только последнее значение. Например:

        Func<string, bool> predicate = null;
        predicate += t => t.Length > 10;
        predicate += t => t.Length < 20;

        bool b = predicate("12345");

Это вернет TRUE, потому что последний вызов функции возвращает true (меньше 20). Для того, чтобы это действительно заработало, вам нужно позвонить:

predicate.GetInvocationList();

, который возвращает массив делегатов. Затем вам нужно убедиться, что они ВСЕ возвращают true, чтобы конечный результат был верным. Имеет смысл?

6 голосов
/ 29 января 2009

Расширение ответа BFree (добавлено +1)

Если вы хотите получить искомое поведение, вам нужно явно связать вместе предикаты. Вот пример

public static Func<Tran, bool> GetPredicate()
{
    Func<Tran, bool> predicate1 = t => t.Response == "00";
    Func<Tran, bool> predicate2 = t => t.Amount < 100;
    return t => predicate1(t) && predicate2(t);
}
1 голос
/ 29 января 2009

Если вы хотите объединить предикаты, попробуйте это;

public static Predicate<T> Combine<T>(params Predicate<T>[] predicates)
{
return t => predicates.All(pr => pr(t));
}

Вы называете это так;

Predicate<string> shortAndSweet = Combine<string>
(
    s => s.Length < 10,  // short,
    s => s == "sweet"    // and sweet
);
0 голосов
/ 29 января 2009

+ = - это синтаксический сахар, специально реализованный для поддержки добавления обработчиков к событиям. Поскольку события - это особый случай делегата, а Func - также делегат, синтаксис выглядит здесь как .

Но вы уверены, что он работает как положено? Под этим я подразумеваю, ожидаете ли вы оценки И или ИЛИ? Как бы вы реализовали обратное, если бы захотели? Вы уверены, что это не просто результат первого? Или последний?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...