Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Concatenating Lambda Functions in C#

Tags:

c#

lambda

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.

like image 779
John Oxley Avatar asked Jan 29 '09 14:01

John Oxley


4 Answers

"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.

like image 91
Chaowlert Chaisrichalermpol Avatar answered Nov 01 '22 12:11

Chaowlert Chaisrichalermpol


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.)

like image 42
Jon Skeet Avatar answered Nov 01 '22 12:11

Jon Skeet


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?

like image 13
BFree Avatar answered Nov 01 '22 13:11

BFree


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);
}
like image 6
JaredPar Avatar answered Nov 01 '22 14:11

JaredPar