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.
A Func
is an expression that can take any number of parameters, including no parameters, and must return a result.
A Predicate
is a specific kind of construct similar to Func
that takes in one of parameter and returns a bool
result.
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.
Executing this code yields the following results.
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.
What should we expect the result to be? Well, we don’t have to guess.
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.
We now get each result for the individual Func
instances.
Predicate Chain Behavior
Like a Func
, Predicate
behave similarly, but need to be defined explicitly, meaning we can’t use var
here.
The result of invoking isItCake
yields a positive outcome.
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.
Each delegate yields a different result.
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.