c# – Why do I need an empty Action delegate<T>

Question:

While studying lambda expressions came across Action and Func delegates. Why do I need the second one, I understood, at least for:

Func<int, int, int> summ = (x, y) => x + y;
Console.WriteLine(summ(10,20));

But what the empty Action<T> not clear.

Give a simple example why you might need it? And why can't we put void in Func<T, TResult> and get the same Action<T> like Func<int, void> for example?

Answer:

There are two worlds – the world of pure functional programming and the real world.

In the pure world of pure functional programming, there is no mutable state and no statements, and everything is an expression. In this ideal world, functions have no side effects, and any construct in a programming language is an expression (i.e., returns a result).

But in the real world, everything is different.

In the real world, and especially in the C # world, there are side effects and there is a wonderful Void type, which is an unusual type that is not compatible with others. In this world, an Action cannot be expressed through a function ( Func ), simply because Func<T, void> prohibited, and the Unit type (similar to the Void type, but convertible to the generic T type) is absent. If this were not the case, then the following code would be valid:

Func<int, Unit> fun = n => System.Console.WriteLine(n);

This does not mean that this rule is universal for all languages ​​of the real world, and even for the .NET world, but for C # it is. The same F # (a language closer to the mathematical world) has the Unit type and can automatically convert the .NET Void to Unit , which makes the given code valid.

TL; DR; Func<int, void> prohibited for historical reasons (which is a shame), because very often you have to fence the converter that converts Func<T> to Action and discard the result.

If we talk about real scenarios, then there are no problems with the applicability of Action types.

  • Parallel.For / Parallel.ForEach : Parallel.For(0, list.Count, n => Console.WriteLine(list[n]);

  • Similar to the previous one – ActionBlock<T> and an action that should be performed by a reasonable number of threads.

  • Task and the action to be executed asynchronously: var task = Task.Run(() => SomeOperation());

  • All sorts of callbacks for logging and other operations that do not return anything.

In general, the availability and popularity of void-return functions determines the applicability of Action<T> . On the good, we should try to keep side effects to a reasonable minimum, but in the real world, this minimum will never be 0.

Scroll to Top