For a long time now, I have been wanting to better understand Action<T>, Func<T1, T2>, and Predicate<T> in C#. I have always understood that they are generic delegates, but I never could get where they are most appropriate to be used when designing a library or framework for other developers to use. I did see examples in libraries like linq, AutoMapper, and RhinoMocks where I thought the usage of Actions or Funcs as extension points made a lot of sense.
So, with this in mind, I'm planning to devote a couple of posts about an exploration into Action<T>, Func<T1, T2>, and Predicate<T>. Hopefully, by recording this journey I'll take more from it and even perhaps help a few other developers to better embrace these tools.
In this post, I just want to do a quick introduction to what Action<T>, Func<T1, T2>, and Predicate<T> are. As I mentioned, these classes seem to be just another way to express a delegate in C#. So why are they needed? There seem to be many answers to that question. First off, they allow for lambda expressions in C#, they are much more terse than anonymous delegates, and in my opinion they express a function pointer in a more direct way than a traditional delegate. Plus, they are fun and make code more expressive.
Action<T>, Func<T1, T2>, Predicate<T> and delegates for that matter are just function pointers. They allow functions to be passed around in code as data.
The place where most people have seen this the most is with C# events. An event needs a delegate type in order to know what type of function it can register. Then when you register that OnLoad event for example, the compiler will only allow a callback method to register against the event that conforms to the correct signature.
In the above example, we are defining a delegate with the common .NET event signature void (object sender, EventArgs e). This comes in handy when we want to wire up a callback function that will listen out for when the event fires. Underneath, the complier is going to make a new collection of OnLoadHandler delegate types and add (+=) or remove (-=) callbacks from the collection. Then when the event is fired, the collection will iterate and call all the registered functions.
So, events is one place that many developers are familiar with delegates. Since Action<T> is a void returning generic delegate, can we use it instead. Yes, and here is the code:
As you can see, we have eliminated the need for a delegate to be defined in the Page class. And what is better, we got rid of that Page.OnLoadHandler scoping issue when we registered the event. The Action<T> can be registered against the event and all the functions stay roughly the same. This is pretty cool, however, with EventHandler<T> out there not that useful. This is more of an example to get warmed up for more to come.
Alright, got it
It's easy to see what is going on here. Action<T> allows us a better way to express a delegate. So now it's time to define these guys.
Actions are delegates that always return void. Each generic type defined (and in the same order) become the type of the parameter in the function.
Funcs are the most expressive generic delegate form. They take at least two generic type arguments because the last one is always the return type of the delegate signature. So, in this case T2 is the type that you want to return.
Predicates are Funcs that always return a boolean value. All of Predicate's generic type arguments define the parameters in its delegate signature.
In the next post, I'll dig a bit deeper into some more typical usages for API design. See ya then!