Sunday, June 23, 2024 21:05

Table of contents >> Delegates, Lambda Expressions, Events > Func and Action

Func and Action

There are five builtin delegate types that you can use in C#: Delegate, MulticastDelegate, Predicate, Func and Action. I’ve already described Delegate in a previous lesson. MulticastDelegate is there only for historic reasons, and it allows us to chain up delegates, but you will rarely or never use it directly. Func, Action and Predicate are generic delegate types that basically allow us to declare any kind of delegate we want, in a generic manner. Let’s declare some:

At this point, the codes will not tell you much, they are just some weird new type variables. However, if we place the cursor on them and hit F12, or if we right click on them and chose “Go to definition”, like this:

you will see this definition for Action:

and this one for Func:
So, if you read my lesson about delegates, it really shouldn’t be difficult to understand at least Action. It is just a normal delegate that takes any method that returns void. Func is a bit harder, because it is a generic type of delegate, but it also shouldn’t be difficult to understand if you read my lesson about generic types. However, Func is a delegate that takes any method that returns something. Like this:
So, whenever you need a delegate that takes any method that returns void, you can always use Action. Whenever you need a delegate that takes any method that returns something, you use Func with the type of that something (bool in my above example)

Of course, both Action and Func have overloads, which means that we can assign to them methods that take one or more parameters. For instance:

In this case, I wanted to assign a method that returns void, and takes two parameters, one of type int, one of type string, void AMethodThatReturnsNothing(int, string). For this, all I had to do was to modify my Action declaration to also take an int and a string: Action<int, string> _action. In fact, Action has 16 overloads, so, you can declare Actions that accept methods that take between 0 and 16 parameters and return void. That is was more than enough for any situation you might encounter.

Func, just like Action, also has 16 overloads, and just like Action, you can assign to it any method that returns something and takes between 0 and 16 parameters of any type. The difference is the fact that since Func must actually return a value, and since it is designed in a generic way, we also must specify the return type, so that the compiler knows what the kind of methods we can assign to it (the methods must return the same type as the delegate signature). In other words, this is the reason why I declared my Func this way: Func<bool> _func. Even if my Func accepts any method that takes no parameters, it must return something, and I am obligated to specify what it should return. In this case, I chose it to return a boolean value, but I could specify any type I want.

If we also need to assign to a Func methods that take parameters too, we specify them just as we are specifying them in the case of Action. However, for Func, the last parameter is ALWAYS the return type, not a parameter of the assigned method. In other words, we can declare a Func like this:

Since AMethodThatReturnsABoolean() takes an int and a string as parameters, and returns a bool, when I assign it to my Func, I need to declare it this way: Func<int, string, bool>. The int and the string are the same types that the method takes as parameters, while the last type in the Func declaration, which I just explained that it always tells us the return type, is a bool, just like the return type of the method assigned to it.

Predicate is another delegate that is identical to Func in every aspect, except that it only returns a boolean value, since it’s signature is this:

Predicate mostly exist because it came before Func. I suppose if the more generic Func would have been implemented first, Predicate would not exist at all. In most cases, you should not be needing to use it. Predicate is mostly used in lists for methods like FindAll() or RemoveAll().

As a conclusion to this lesson, whenever you need a delegate, you can either declare it directly as a Delegate, or you can use Action whenever you need to assign methods that take between 0 and 16 parameters and returns void, or Func when you need the same thing as an Action, but also a return value.

Tags: , ,

Leave a Reply

Follow the white rabbit