Action
C#
programming
methods
return value

How to return value from Action?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

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

csharp
1// Action — returns void
2Action greet = () => Console.WriteLine("Hello");
3Action<string> greetName = (name) => Console.WriteLine($"Hello, {name}");
4Action<int, int> add = (a, b) => Console.WriteLine(a + b);
5
6// Func — returns a value (last type parameter is the return type)
7Func<int> getNumber = () => 42;
8Func<string, int> getLength = (s) => s.Length;
9Func<int, int, int> multiply = (a, b) => a * b;
10
11int result = multiply(3, 4);  // 12

If you need a return value, Func<TResult> is the correct delegate type.

csharp
1// WRONG — trying to get a return value from Action
2Action calculate = () => { /* can't return anything */ };
3
4// CORRECT — use Func<T>
5Func<int> calculate = () => 42;
6int result = calculate();  // 42
7
8// With parameters
9Func<string, string, string> concatenate = (a, b) => $"{a} {b}";
10string fullName = concatenate("John", "Doe");  // "John Doe"

If you control the API, change Action parameters to Func<T>:

csharp
1// Before — no way to get result
2public void Execute(Action operation)
3{
4    operation();
5}
6
7// After — can return result
8public T Execute<T>(Func<T> operation)
9{
10    return operation();
11}
12
13// Usage
14int result = Execute(() => 42);

Solution 2: Captured Variable (Closure)

When you cannot change the delegate type (e.g., third-party API), capture a variable in the closure.

csharp
1int result = 0;
2
3Action compute = () =>
4{
5    result = ExpensiveCalculation();  // Write to captured variable
6};
7
8compute();
9Console.WriteLine(result);  // Contains the computed value
csharp
1// Real-world example: extracting data from an Action-based callback API
2string responseBody = null;
3
4Action<HttpClient> fetchData = (client) =>
5{
6    var response = client.GetStringAsync("https://api.example.com/data").Result;
7    responseBody = response;  // Captured variable
8};
9
10fetchData(new HttpClient());
11Console.WriteLine(responseBody);  // Contains the response

Solution 3: out Parameter via Wrapper

csharp
1public delegate void ActionWithResult<T>(out T result);
2
3public static T ExecuteWithResult<T>(ActionWithResult<T> action)
4{
5    action(out T result);
6    return result;
7}
8
9// Usage
10int value = ExecuteWithResult((out int result) =>
11{
12    result = 42;
13});
14Console.WriteLine(value);  // 42

Solution 4: Convert Action to Func with a Wrapper

csharp
1public static Func<T> ToFunc<T>(Action action, T defaultResult)
2{
3    return () =>
4    {
5        action();
6        return defaultResult;
7    };
8}
9
10// When you need to pass an Action where Func is expected
11Action sideEffect = () => Console.WriteLine("Done");
12Func<bool> wrapped = ToFunc(sideEffect, true);
13bool result = wrapped();  // Prints "Done", returns true

Solution 5: Using Task for Async Scenarios

csharp
1// Action-based async pattern
2public async Task<int> ExecuteAsync(Action<Action<int>> operation)
3{
4    var tcs = new TaskCompletionSource<int>();
5    operation(result => tcs.SetResult(result));
6    return await tcs.Task;
7}
8
9// Usage
10int result = await ExecuteAsync(callback =>
11{
12    // Simulate async work
13    int computed = 42;
14    callback(computed);  // Signal completion with result
15});

When Each Approach Is Appropriate

ScenarioApproach
You control the APIChange Action to Func<T>
Third-party callback APICaptured variable (closure)
Need thread-safe result passingTaskCompletionSource<T>
Simple one-off extractionout parameter wrapper
Adapting Action for Func-expecting APIWrapper function

Common Pitfalls

  • Using Action when Func is what you need: If you are designing an API and the operation produces a result, use Func<TResult> from the start. Trying to extract return values from Action is 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 Action may complete on a different thread, and reading the variable without synchronization causes race conditions. Use TaskCompletionSource<T> or Interlocked for cross-thread result passing.
  • Calling .Result on tasks inside Action: Using .Result or .Wait() on async operations inside an Action can deadlock, especially in UI or ASP.NET contexts with a synchronization context. Use async/await with Func<Task<T>> instead.
  • Confusing Action<T> with Func<T>: Action<T> takes a parameter of type T and returns void. Func<T> takes no parameters and returns T. Func<T, TResult> takes T and returns TResult. 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 Action to simulate return values adds unnecessary complexity. Refactor to Func<T> when feasible.

Summary

  • Action returns void — use Func<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 .Result and .Wait() inside Action delegates to prevent deadlocks

Related reading
Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.