Объединение лямбда-функций в С#
Используя С# 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;
}
Когда я говорю "предикат + =", что это значит? предикат - = ничего не делает, и ^ =, & =, * =,/= не нравятся компилятору.
Компилятор не любит 'predicate = predicate + t = > t.Response....'.
На что я наткнулся? Я знаю, что он делает, но как он это делает?
Если кто-то хочет углубиться в более сложные лямбды, сделайте это.
Ответы
Ответ 1
"delegate + = method" - это оператор для многоадресного делегирования для объединения метода для делегирования.
С другой стороны, "delegate - = method" - это оператор для удаления метода из делегата.
Это полезно для Action.
Action action = Method1;
action += Method2;
action += Method3;
action -= Method2;
action();
В этом случае будут выполняться только Method1 и Method3, Method2 не будет запущен, потому что вы удалите этот метод перед вызовом делегата.
Если вы используете делегат multicast с Func, результатом будет последний метод.
Func<int> func = () => 1;
func += () => 2;
func += () => 3;
int result = func();
В этом случае результат будет равен 3, так как метод "() = > 3" - последний метод, добавленный к делегату. В любом случае будет вызываться весь метод.
В вашем случае будет эффективен метод "t = > t.Amount < 100".
Если вы хотите совместить предикат, я предлагаю эти методы расширения.
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;
}
EDIT: исправьте имя методов расширения, как подсказывает Кейт.
Ответ 2
Когда вы используете + = или - = при делегировании, это просто дает вызов 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);
EDIT: здесь более общее решение, которое довольно забавно, если немного странно...
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 и т.д.)
Ответ 3
Собственно, это не работает. Попробуйте протестировать его в случае, когда первое условие НЕИСПРАВНО, а второе. Вы обнаружите, что оно вернет true. Причина в том, что при работе с делегатами многоадресной передачи, возвращающими значения, возвращается только последнее значение. Например:
Func<string, bool> predicate = null;
predicate += t => t.Length > 10;
predicate += t => t.Length < 20;
bool b = predicate("12345");
Это вернет TRUE, потому что последний вызов функции возвращает true (менее 20). Чтобы действительно заставить его работать, вам нужно позвонить:
predicate.GetInvocationList();
который возвращает массив делегатов. Затем вам нужно убедиться, что они ВСЕ вернутся к истине, чтобы конечный результат был правдой. Есть смысл?
Ответ 4
Расширение ответа BFree (+ 1'd)
Если вы хотите получить поведение, которое вы ищете, вам нужно явно связать предикаты вместе. Вот пример
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);
}
Ответ 5
Если вы хотите совместить предикаты, попробуйте это:
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
);
Ответ 6
+ = синтаксический сахар, специально реализованный для поддержки добавления обработчиков к событиям. Поскольку события - это всего лишь особый случай делегирования, а Func также является делегатом, синтаксис, похоже, работает здесь.
Но вы уверены, что он работает так, как ожидалось? Под этим я имею в виду, вы ожидаете оценки AND или OR? Как бы вы реализовали противоположное, если хотите? Вы уверены, что это не просто возвращение результата первого? Или последнее?