Search code examples
c#async-awaitanonymous

What are differences between the 3 calls for async Func<Task<T>> in c#?


What are differences between the 3 calls inside method WhatDifferences?

Here is test code:

async Task WhatDifferences(Context context)
{
    await ActionAsync(context, async x => await IsOddAsync(x).ConfigureAwait(false));
    await ActionAsync(context, x => IsOddAsync(x));
    await ActionAsync(context, IsOddAsync);
}

async Task<T> ActionAsync<T>(Context context, Func<Context, Task<T>> action)
{
    return await action(context).ConfigureAwait(false);
}

async Task<bool> IsOddAsync(Context context)
{
    return await Task.Run(() => context.Count++ % 2 == 1).ConfigureAwait(false);
}

class Context
{
    public int Count { get; set; }
}

I'm trying to decide which one to use in my codebase and based on my knowledge all 3 behave the same.

The question is different with What's the method signature for passing an async delegate?

You may know my concern if I show more logic

async Task<T> ActionAsync<T>(Context context, Func<Context, Task<T>> action)
{
    using (var transaction = new TransactionScope())
    {
        //do some async logic before action
        var result = await action(context).ConfigureAwait(false);
        //do other async validation logic after action
        return result;
    }
}

Solution

  • I'm trying to decide which one to use in my codebase and based on my knowledge all 3 behave the same.

    In this specific instance, this is essentially true.

    This one creates a delegate that refers to the IsOddAsync method:

    await ActionAsync(context, IsOddAsync);
    

    This one creates a method for the lambda expression and the delegate refers to that compiler-generated method:

    await ActionAsync(context, x => IsOddAsync(x));
    

    And this one does the same, but for an asynchronous lambda, so the compiler-generated method also has an async state machine:

    await ActionAsync(context, async x => await IsOddAsync(x).ConfigureAwait(false));
    

    In general, your question boils down to two questions:

    1. Should I use method groups instead of lambdas? Yes, you should. There's no disadvantage to doing so, and it's a tiny bit more efficient, shorter code, without any impact on maintainability.
    2. Should I elide async/await or keep the keywords in? This one is more nuanced.

    Eliding async in this particular case is fine, because all the async lambda is doing is calling a single method and passing its parameter. There's no possibility of exceptions being thrown from the lambda before or after the call to IsOddAsync.

    However, if your lambda is more complex - doing operations on x before passing it to IsOddAsync, or doing operations on the result, or using a using block, then you'd want to keep the async/await keywords for maximum maintainability. More information here.