Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why async / await allows for implicit conversion from a List to IEnumerable?

I've just been playing around with async/await and found out something interesting. Take a look at the examples below:

// 1) ok - obvious
public Task<IEnumerable<DoctorDto>> GetAll()
{
    IEnumerable<DoctorDto> doctors = new List<DoctorDto>
    {
        new DoctorDto()
    };

    return Task.FromResult(doctors);
}

// 2) ok - obvious
public async Task<IEnumerable<DoctorDto>> GetAll()
{
    IEnumerable<DoctorDto> doctors = new List<DoctorDto>
    {
        new DoctorDto()
    };

    return await Task.FromResult(doctors);
}

// 3) ok - not so obvious
public async Task<IEnumerable<DoctorDto>> GetAll()
{
    List<DoctorDto> doctors = new List<DoctorDto>
    {
        new DoctorDto()
    };

    return await Task.FromResult(doctors);
}

// 4) !! failed to build !!
public Task<IEnumerable<DoctorDto>> GetAll()
{
    List<DoctorDto> doctors = new List<DoctorDto>
    {
        new DoctorDto()
    };

    return Task.FromResult(doctors);
}

Consider cases 3 and 4. The only difference is that 3 uses async/await keywords. 3 builds fine, however 4 gives an error about implicit conversion of a List to IEnumerable:

Cannot implicitly convert type 
'System.Threading.Tasks.Task<System.Collections.Generic.List<EstomedRegistration.Business.ApiCommunication.DoctorDto>>' to 
'System.Threading.Tasks.Task<System.Collections.Generic.IEnumerable<EstomedRegistration.Business.ApiCommunication.DoctorDto>>'  

What is it that async/await keywords change here?

like image 883
Andrzej Gis Avatar asked Aug 07 '14 12:08

Andrzej Gis


People also ask

What is one benefit of using async await?

The biggest advantage of using async and await is, it is very simple and the asynchronous method looks very similar to a normal synchronous methods. It does not change programming structure like the old models (APM and EAP) and the resultant asynchronous method look similar to synchronous methods.

What is the advantage of async await advantages?

with async / await , you write less code and your code will be more maintainable than using the previous asynchronous programming methods such as using plain tasks. async / await is the newer replacement to BackgroundWorker , which has been used on windows forms desktop applications.

What is the purpose of async await keywords?

The async keyword turns a method into an async method, which allows you to use the await keyword in its body. When the await keyword is applied, it suspends the calling method and yields control back to its caller until the awaited task is complete. await can only be used inside an async method.

What happens when async method is not awaited?

The call to the async method starts an asynchronous task. However, because no Await operator is applied, the program continues without waiting for the task to complete. In most cases, that behavior isn't expected.


3 Answers

Task<T> is simply not a covariant type.

Although List<T> can be converted to IEnumerable<T>, Task<List<T>> cannot be converted to Task<IEnumerable<T>>. And In #4, Task.FromResult(doctors) returns Task<List<DoctorDto>>.

In #3, we have:

return await Task.FromResult(doctors)

Which is the same as:

return await Task.FromResult<List<DoctorDto>>(doctors)

Which is the same as:

List<DoctorDto> result = await Task.FromResult<List<DoctorDto>>(doctors);
return result;

This works because List<DoctorDto> can be converted IEnumerable<DoctorDto>.

like image 136
avo Avatar answered Oct 16 '22 15:10

avo


Just think about your types. Task<T> is not variant, so it's not convertible to Task<U>, even if T : U.

However, if t is Task<T>, then the type of await t is T, and T can be converted to U if T : U.

like image 35
Stephen Cleary Avatar answered Oct 16 '22 15:10

Stephen Cleary


Clearly you understand why List<T> can at least be returned as IEnumerable<T>: simply because it implements that interface.

Also clearly, the 3rd example is doing something "extra" that the forth one isn't. As others have said, the 4th fails because of the lack of co-variance (or contra-, I can never remember which way they go!), because you are directly trying to offer an instance of Task<List<DoctorDto>> as an instance of Task<IEnumerable<DoctorDto>>.

The reason the 3rd passes is because await adds a big bunch of "backing code" to get it to work as intended. This code resolves the Task<T> into T, such that return await Task<something> will return the type closed in the generic Task<T>, in this case something.

That the method signature then returns Task<T> and it works is again solved by the compiler, which requires Task<T>, Task, or void for async methods and simply massages your T back into a Task<T> as part of all the background generated asyn/await continuation gubbins.

It is this added step of getting a T from await and needing to translate it back into a Task<T> that gives it the space it needs to work. You are not trying to take an existing instance of a Task<U> to satisfy a Task<T>, you are instead creating a brand new Task<T>, giving it a U : T, and on construction the implicit cast occurs as you would expect (in exactly the same way as you expect IEnumerable<T> myVar = new List<T>(); to work).

Blame / thank the compiler, I often do ;-)

like image 7
Adam Houldsworth Avatar answered Oct 16 '22 15:10

Adam Houldsworth