Func vs Action in .NET

From time to time, we face a situation when we need to define a variable that points to a method or an anonymous function. It could be a part of business logic or some test case from unit tests. C# has a particular type that allows pointing to a specific method with a particular list of parameters and return type. Instead of defining a new delegate type, C # contains build-in generic delegates such as Action and Func.

Func vs Action

Both Action and Func allow us to create a pointer to an existing method. Let`s clarify the difference between these two classes.

Action

Action points to any method that does not return value, and it accepts up to 16 input parameters. In most cases, methods have a shortlist of parameters, but C# developers decided to cover even a rare case when methods have 16 input parameters.

Let`s take a look at how to define Action and use it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class ExamplesForAction
{
public void RunExamples()
{
// declaration
Action lambdaAction = () => {
Console.WriteLine("Lambda function");
};
Action methodAction = Method;
Action<int> methodWithParams = MethodWithParams;

// invocation
lambdaAction();
methodAction();
methodWithParams(10);
}

public void MethodWithParams(int paramName)
{
Console.WriteLine("Method with params");
}
public void Method() {
Console.WriteLine("Method without params");
}
}

The Action could be initialized with an existing method with the same signature that the Action has. An attempt to assign a method with a different signature causes a compilation error.

When we initialize an Action variable, we can invoke the nested method the same way as we invoke ordinal methods using brackets after the method name, in our case after the variable name.

Func

We intended to use Func the same way as we use Action. Unlike Action, it points to methods with a return value, but they both have in common that they accept up to 16 parameters.

Let`s take a look at Func in code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class ExamplesForFunc
{
public async Task RunExamples()
{
// causes compilation error
// Func f = () => { };

// declaration
Func<int> func = () =>
{
Console.WriteLine("Lambda function");
return 1;
};
Func<int> methodFunc = Method;
Func<int, int> methodWithParams = MethodWithParams;
Func<Task> asyncMethod = MethodAsync;

// invocation
var result1 = func();
var result2 = methodFunc();
var result3 = methodWithParams(2);
await asyncMethod();
}
public Task MethodAsync()
{
Console.WriteLine("Async Method");
return Task.CompletedTask;
}
public int Method()
{
Console.WriteLine("Method with params");
return 1;
}
public int MethodWithParams(int paramName)
{
Console.WriteLine("Method without params");
return 1;
}
}

As you see, Func and Action declaration and invocation have a lot in common. We can assign lambda expressions to them or use an existing method.

If you are working with asynchronous code, you should use Func for proper result handling. Func returns Task object. Later in our code, we can await Task to wait for method execution to complete.

We are not limited in delegate usage; we can store the pointer to some method, invoke that method later or pass the method pointer as a parameter to another method.

Working with .NET, we face a lot of Func usage in Linq methods. Almost all extension methods accept Func<TSource, TValue> delegate as an input parameter.

Conclusion

Both types, Action, and Func, have a common purpose, and it depends only on the method return type which delegate type to choose. If the method has a return type different from the void, we should use Func, and only for methods with return type void, we should choose Action.