How to return value from Action?
Data Structures & Algorithms practice on Codemia
Step through 300+ coding interview problems with animated visualisers that show the data structure changing as the code runs. The Blind 75 are free.
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
Related reading
- How to return value with anonymous method?
- How to run Command Prompt commands from C
- How to select all text in Winforms NumericUpDown upon tab in?
- How to select different app.config for several build configurations
- How to select .NET 4.5.2 as a target framework in Visual Studio
- How to select only the records with the highest date in LINQ
- How to send an email in .Net according to new security policies?
- How to send an HTTPS GET Request in C

OOD Fundamentals
Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.
View the courseData Structures & Algorithms practice on Codemia
Step through 300+ coding interview problems with animated visualisers that show the data structure changing as the code runs. The Blind 75 are free.