Skip to main content

Fixed: "error CS8031: Async lambda expression converted to a 'Task' returning delegate cannot return a value"

· 4 min read
Jagdish Kumawat
Founder @ Dewiride

Wrapping an endpoint's work in strategy.ExecuteAsync gave error CS8031: Async lambda expression converted to a 'Task' returning delegate cannot return a value. The code looks right, and it returns values on purpose. The compiler just can't work out one type for those values, and a small addition tells it.

What I Was Doing​

I was moving a transaction into EF Core's execution strategy, in a .NET 10 minimal API. The endpoint returns either 201 Created or 404 Not Found:

src/Dewiride.Inventory.Api/Features/Stock/StockEndpoints.cs (excerpt)
var strategy = db.Database.CreateExecutionStrategy();

return await strategy.ExecuteAsync(
async ct =>
{
// ...
if (changed == 0)
{
return TypedResults.NotFound();
}

// ...
return TypedResults.Created(/* ... */);
},
cancellationToken);
Terminal
dotnet build

What I Expected​

Build succeeded.

What I Got​

Output
Build FAILED.
src/Dewiride.Inventory.Api/Features/Stock/StockEndpoints.cs(51,21): error CS8031: Async lambda expression converted to a 'Task' returning delegate cannot return a value
src/Dewiride.Inventory.Api/Features/Stock/StockEndpoints.cs(75,17): error CS8031: Async lambda expression converted to a 'Task' returning delegate cannot return a value

dotnet build failing with error CS8031 on the two return lines inside the lambda.

What I Checked​

The lines it names. Both are return statements, one for each result type: NotFound and Created<T>.

What ExecuteAsync offers. The extension methods on IExecutionStrategy include one for a Func<CancellationToken, Task>, which returns no value, and one for Func<CancellationToken, Task<TResult>>. The message mentions a plain Task, so the compiler had picked the first.

Why It Happens​

To use the Task<TResult> overload, the compiler has to infer TResult from your return statements. NotFound and Created<T> have no common type it can pick. They both convert to Results<Created<T>, NotFound>, but nothing here mentions that type. With inference failed, only the overload with no result is left, and a lambda for that one can't return a value.

The Fix​

State the lambda's return type:

src/Dewiride.Inventory.Api/Features/Stock/StockEndpoints.cs (excerpt)
return await strategy.ExecuteAsync(
async Task<Results<Created<StockChangeResponse>, NotFound>> (CancellationToken ct) =>
{
// ...
},
cancellationToken);

Each return now converts to that type, as it does in a normal method. The parameter's type is optional here: (ct) built just as well, and I kept (CancellationToken ct) because it reads better.

After the Fix​

Output
Build succeeded.
0 Warning(s)
0 Error(s)

dotnet build reporting Build succeeded after the lambda's return type was stated.

Common Mistakes​

MistakeWhat happensDo this instead
Casting each return to IResultIt compiles, and OpenAPI loses the result typesState the union type
Moving everything outside the lambdaNo retry for itKeep the whole transaction inside

Troubleshooting​

SymptomLikely causeFix
A conversion error on one returnThat type isn't in the Results<...> listAdd it
The lambda is hard to readThe union type is longMove the body to a private method, and call it

FAQ​

Could I use a method instead of a lambda?​

Yes. A private method with a normal return type works, and it's easier to read: strategy.ExecuteAsync(ct => ReceiveOnceAsync(productId, request, db, clock, ct), cancellationToken).

Conclusion​

CS8031 here means the compiler couldn't infer one return type from your return statements. Write the type on the lambda, as in async Task<Results<...>> (CancellationToken ct) => ....

Additional Resources​

Stay Updated

Subscribe to our newsletter for the latest tutorials, tech insights, and developer news.

By subscribing, you agree to our privacy policy. Unsubscribe at any time.