How to return value from Action?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
In C#, Action is a delegate type that represents a method returning void — it performs work but does not return a value. If you need a delegate that returns a value, use Func<TResult> instead. However, there are legitimate scenarios where you have an existing Action-based API and need to extract a result. The workarounds include using Func<T> (the correct solution), out parameters, captured variables (closures), or wrapping the Action in a Task.
Action vs Func — The Core Difference
If you need a return value, Func<TResult> is the correct delegate type.
Solution 1: Use Func<T> Instead of Action (Recommended)
If you control the API, change Action parameters to Func<T>:
Solution 2: Captured Variable (Closure)
When you cannot change the delegate type (e.g., third-party API), capture a variable in the closure.
Solution 3: out Parameter via Wrapper
Solution 4: Convert Action to Func with a Wrapper
Solution 5: Using Task for Async Scenarios
When Each Approach Is Appropriate
| Scenario | Approach |
| You control the API | Change Action to Func<T> |
| Third-party callback API | Captured variable (closure) |
| Need thread-safe result passing | TaskCompletionSource<T> |
| Simple one-off extraction | out parameter wrapper |
| Adapting Action for Func-expecting API | Wrapper function |
Common Pitfalls
- Using
ActionwhenFuncis what you need: If you are designing an API and the operation produces a result, useFunc<TResult>from the start. Trying to extract return values fromActionis a workaround, not a pattern. - Thread safety with captured variables: When using closures to capture results across threads, the captured variable is not thread-safe. The
Actionmay complete on a different thread, and reading the variable without synchronization causes race conditions. UseTaskCompletionSource<T>orInterlockedfor cross-thread result passing. - Calling
.Resulton tasks inside Action: Using.Resultor.Wait()on async operations inside anActioncan deadlock, especially in UI or ASP.NET contexts with a synchronization context. Useasync/awaitwithFunc<Task<T>>instead. - Confusing
Action<T>withFunc<T>:Action<T>takes a parameter of typeTand returns void.Func<T>takes no parameters and returnsT.Func<T, TResult>takesTand returnsTResult. Mixing these up causes compile errors that may be confusing. - Over-engineering with wrappers when refactoring is possible: If you can change the delegate type, just change it. Adding wrapper classes, captured variables, and adapter patterns around
Actionto simulate return values adds unnecessary complexity. Refactor toFunc<T>when feasible.
Summary
Actionreturnsvoid— useFunc<TResult>when you need a return value- If you cannot change the delegate type, use a captured variable (closure) to extract the result
- Use
TaskCompletionSource<T>for async scenarios with callback-based APIs Func<T>is the idiomatic C# approach — prefer it over workarounds- Avoid
.Resultand.Wait()insideActiondelegates to prevent deadlocks

