It is always nice to learn something new, regardless of our experience. In this short but very cool post, we’ll learn more about a little known feature having to do with Action, Func, and Predicate in .NET.

A delegate is a type that represents a reference to a method with a particular set of parameters and return type. Delegates are one of the fundamental building blocks of the .NET framework, and they received a significant upgrade with the introduction of the Language Integrated Query (LINQ) syntax. You may also here delegates referred to as Lambda Expressions.

There are three recognized Lambda expressions: Actions, Funcs, and Predicates.

An Action is an expression that takes no parameters but executes a statement.

Action hello = () => Console.WriteLine("hello");

A Func is an expression that can take any number of parameters, including no parameters, and must return a result.

Func<int, int> plusOne = (i) => i + 1;

A Predicate is a specific kind of construct similar to Func that takes in one of parameter and returns a bool result.

Predicate<string> isItCake = 
    s =>
    {
        Console.WriteLine($"Checking {s} with \"cake\"");
        return s?.Contains("cake", StringComparison.OrdinalIgnoreCase) == true;
    };

As mentioned before, all of these lambda expressions inherit their behavior from the delegate type.

Chaining Behaviors

Delegates allow developers to add as many handlers to a delegate instance utilizing the += operator. Let’s walk through the invocation behavior of each type.

Action Chain Behavior

With an Action, we can chain any number of actions after the first assignment.

Action Hello;

Hello = () =>  Console.WriteLine("Hello");
Hello += () => Console.WriteLine("World");
Hello += () => Console.WriteLine(".NET & Khalid\n");

Hello();

Executing this code yields the following results.

Hello
World
.NET & Khalid

As we can see, the app executes the actions based on their registration.

Func Chain Behavior

A Func behaves slightly differently, as it could return a result. Let’s look at an example.

Func<string> groceries;

groceries = () =>
{
    Console.WriteLine("1 Potato");
    return "1 Potato";
};
groceries += () =>
{
    Console.WriteLine("2 Apples");
    return "2 Apples";
};
groceries += () =>
{
    Console.WriteLine("3 Bagels");
    return "3 Bagels";
};

// invoking groceries
var pick = groceries();
Console.WriteLine($"The pick is {pick}\n");

What should we expect the result to be? Well, we don’t have to guess.

1 Potato
2 Apples
3 Bagels
The pick is 3 Bagels

Executing the chained Func will always return the last result. We could get the individual results by using the GetInvocationList method and perform each Func independently.

foreach (var @delegate in groceries.GetInvocationList())
{
    var item = (Func<string>) @delegate;
    Console.WriteLine($"purchasing: {item()}");
}

We now get each result for the individual Func instances.

1 Potato
purchasing: 1 Potato
2 Apples
purchasing: 2 Apples
3 Bagels
purchasing : 3 Bagels

Predicate Chain Behavior

Like a Func, Predicate behave similarly, but need to be defined explicitly, meaning we can’t use var here.

Predicate<string> isItCake = 
    s =>
    {
        Console.WriteLine($"Checking {s} with \"cake\"");
        return s?.Contains("cake", StringComparison.OrdinalIgnoreCase) == true;
    };

isItCake += 
    s =>
    {
        Console.WriteLine($"Checking {s} with \"bread\"");
        return s?.Contains("bread", StringComparison.OrdinalIgnoreCase) == true;
    };

var bananaBread = "banana bread";
var result =
   isItCake(bananaBread);

The result of invoking isItCake yields a positive outcome.

Checking banana bread with "cake"
Checking banana bread with "bread"
Is banana bread cake? Yes

As we may have noticed, the result is that of the last registered Predicate. We can use the GetInvocationList method to iterate over each predicate instance.

foreach (var @delegate in isItCake.GetInvocationList())
{
    var item = (Predicate<string>) @delegate;
    Console.WriteLine($"is it Cake : {item(bananaBread)}");
}

Each delegate yields a different result.

Checking banana bread with "cake"
is it Cake : False
Checking banana bread with "bread"
is it Cake : True

Conclusion

Chaining is a powerful feature of .NET delegates and many developers may not realize that when passed an Action, Func, or Predicate they may be getting more than one. When writing libraries that pass these types around, it may be necessary for library authors to check the GetInvocationList and react accordingly: throw an exception, invoke each instance and aggregate the results, or do nothing different.

I hope you learned something new and exciting, and please leave a comment if you have any thoughts on the subject.