Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can an async method with return type ValueTask await a regular Task without generating a compiler error?

The following code does not compile

public ValueTask Foo()
{
    return Task.Delay(1000);
}

but yields an Error CS0029: Cannot implicitly convert type 'System.Threading.Tasks.Task' to 'System.Threading.Tasks.ValueTask as expected.

However, this

public async ValueTask Bar()
{
    await Task.Delay(1000);
}

does compile fine.

I was just wondering how this works. Is this all down to compiler magic and its async-await syntactic sugar or is there something else going on?

For context: I came across this when implementing IAsyncDisposable.DisposeAsync().

like image 632
Joerg Avatar asked Oct 12 '25 10:10

Joerg


2 Answers

Is this all down to compiler magic and its async-await syntactic sugar?

In short, yes. Whenever you await, the compiler needs to generate a state machine for that method. The task returned from the method then, is one that "represents" the state machine, rather than the single task that you are awaiting.

As a result, it doesn't matter what tasks you are awaiting anymore. The compiler just has to build the state machine according to where your awaits are in your method, and then build a new task.

Compare the code generated from the following snippets on SharpLab:

1:

async Task Bar()
{
    await Task.Delay(1000);
}

2:

async ValueTask Bar()
{
    await Task.Delay(1000);
}

The only substantial difference is that one uses AsyncTaskMethodBuilder to build the task being returned, and the other using AsyncValueTaskMethodBuilder.

For more details about the difference of awaiting a task vs directly returning the task, see this chain of duplicates.

like image 102
Sweeper Avatar answered Oct 14 '25 01:10

Sweeper


Title doesn't match what's being asked. In fact, the question proves it's possible.

If you want to return ValueTask that represents a Task whithout having a method turned into a state machine, you can:

public ValueTask Foo()
{
    return new ValueTask(Task.Delay(1000));
}
like image 20
Paulo Morgado Avatar answered Oct 14 '25 00:10

Paulo Morgado