Using C# 3.5 I wanted to build up a predicate to send to a where clause piece by piece. I have created a very simple Console Application to illustrate the solution that I arrived at. This works perfectly. Absolutely perfectly. But I have NO idea how or why.
public static Func<Tran, bool> GetPredicate()
{
Func<Tran, bool> predicate = null;
predicate += t => t.Response == "00";
predicate += t => t.Amount < 100;
return predicate;
}
When I say 'predicate +=', what does that mean? predicate -= appears to do nothing and ^=, &=, *=, /= aren't liked by the compiler.
The compiler doesn't like 'predicate = predicate + t => t.Response....' either.
What have I stumbled on? I know what it does, but how does it do it?
If anyone wants to go delve into more complicated lambda's, please do so.
"delegate += method" is operator for multicast delegate to combine method to delegate. In the other hand "delegate -= method" is operator to remove method from delegate. It is useful for Action.
Action action = Method1;
action += Method2;
action += Method3;
action -= Method2;
action();
In this case, only Method1 and Method3 will run, Method2 will not run because you remove the method before invoke the delegate.
If you use multicast delegate with Func, the result will be last method.
Func<int> func = () => 1;
func += () => 2;
func += () => 3;
int result = func();
In this case, the result will be 3, since method "() => 3" is the last method added to delegate. Anyway all method will be called.
In your case, method "t => t.Amount < 100" will be effective.
If you want to combine predicate, I suggest these extension methods.
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);
}
Usage
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: correct the name of extension methods as Keith suggest.
When you use += or -= when delegate types, that just gives a call to Delegate.Combine
and Delegate.Remove
.
The important thing about multicast delegates is that the return value of all but the last executed delegate is ignored. They all get executed (unless an exception is thrown), but only the last return value is used.
For predicates, you might want to do something like:
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));
}
You'd then do:
Func<string, bool> predicate = And<string>(
t => t.Length > 10,
t => t.Length < 20);
EDIT: Here's a more general solution which is quite fun, if a bit bizarre...
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);
}
So you could then implement And as:
public static Func<T, bool> And<T>(params Func<T, bool>[] predicates) {
return Combine<T, bool>((x, y) => x && y, predicates);
}
(I personally prefer this over using GetInvocationList()
, because you end up with a predicate you can pass to other bits of LINQ etc.)
Actually, that doesn't work. Try to test it with a case where the first condition FAILS but the second one passes. You'll find that it'll return true. The reason is, because when dealing with multicast delegates that return values, only the last value is returned. For example:
Func<string, bool> predicate = null;
predicate += t => t.Length > 10;
predicate += t => t.Length < 20;
bool b = predicate("12345");
This will return TRUE because the last function call returns true (it's less than 20). In order to really make it work, you need to call:
predicate.GetInvocationList();
which returns an array of delegates. You then need to make sure they ALL return true, for the final result to be true. Make sense?
Expanding on BFree's answer (+1'd it)
If you want to get the behavior you're looking for, you'll need to explicitly chain the predicates together. Here is an example
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);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With