Func delegate with no return type
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
In C#, Func always represents a delegate that returns a value. If you need a delegate with no return value, the correct type is Action. This sounds simple, but confusion appears often when moving between lambda expressions, async callbacks, events, and reusable API signatures.
Func Always Has a Return Type
The generic signature of Func ends with the return type parameter. All earlier type parameters are input arguments.
Examples:
Func<int>means no inputs, returnsint.Func<int, string>means oneintinput, returnsstring.Func<int, int, bool>means twointinputs, returnsbool.
If a lambda does not return a value, it cannot be assigned to Func.
Use Action for No Return Delegates
Action is the delegate family for methods that return void.
Common Action forms:
Actionfor zero inputs.Action<T>for one input.Action<T1, T2, ...>for multiple inputs.
In practical code, this maps naturally to side effect callbacks like logging, publishing notifications, and updating UI state.
That difference is useful for code review too. When a parameter is typed as Action, readers immediately know the callback is being invoked for its effect rather than for a computed result that must be captured.
How to Choose Between Func and Action
Use this rule:
- If callback computes and returns something, use
Func. - If callback only performs work, use
Action.
Example API design:
Clear delegate intent improves readability and reduces misuse by API consumers.
Lambdas and Method Groups
Delegates can be created from lambda expressions or method groups.
Method groups are useful when named methods are easier to test or reuse than inline lambdas.
Delegates in LINQ and Async Code
LINQ commonly uses Func delegates for projections and filters.
Async flows may mix both styles:
Here, the asynchronous producer returns data through Func, while the consumer side effect uses Action.
Custom Delegates Still Have a Place
Func and Action cover many use cases, but custom delegates can improve intent when parameter meaning is domain specific.
Named delegates can make complex callback signatures clearer than generic tuples of type parameters.
Another example is when a callback has semantic meaning in a public API, such as MessageHandler or ConnectionLostHandler. A named delegate communicates intent better than a generic Action<string>, especially in shared libraries.
Async Variants and Return Semantics
When callbacks are asynchronous, the same principle applies. A callback that returns Task or Task[T] should use Func because there is still a return value, even if it is asynchronous.
Using Action for asynchronous lambdas can hide returned tasks and make error handling harder. Prefer explicit Func signatures so callers can await completion and handle failures correctly.
Common Pitfalls
- Trying to represent a
voidcallback withFunc. - Using
Actionwhere a result should be returned and tested. - Creating complex delegate signatures without documenting callback contract.
- Hiding important return semantics inside side effect delegates.
- Overusing custom delegates when
FuncorActionis already clear.
Summary
Funcalways includes a return type and is for value producing callbacks.Actionis the correct delegate family for no return callbacks.- Choose delegate type based on callback intent, not style preference.
- Use method groups or lambdas depending on readability and reuse needs.
- Keep callback contracts explicit so delegate based APIs remain maintainable.

