Currently I have a method with the signature public Task<IEnumerable<Descriptor>> GetAllDescriptors()
where Descriptor
is a public class I created. I then have various tasks that I call within this method like so:
var allDescriptors = Task.WhenAll(GetMovieDescriptorsAsync, GetSongDescriptorsAsync);
Since GetMovieDescriptorsAsync
& GetSongDescriptorsAsync
both return Task<Descriptor>
I know that the variable type of allDescriptors
is Task<Descriptor[]>
which is great. However if I try return allDescriptors
I get the error:
Cannot implicitly convert type 'System.Threading.Tasks.Task<Descriptor[]>' to 'System.Threading.Tasks.Task<System.Collections.Generic.IEnumerable<Descriptor>>'
However, if I change the method signature to public async Task<IEnumerable<Descriptor>> GetAllDescriptors()
and then I try return await allDescriptors
it works and I get the desired <IEnumerable<CompletionInfo>
in the calling function:
var descriptors = await GetAllDescriptors();
Why is that and how can I return the expected IEnumerable<Descriptor>
without having to use async and await?
CodePudding user response:
The problem (and solution) you are describing is covariance
. IEnumerable<out T>
is a covariant interface, which means you can assign IEnumerable<string>
into IEnumerable<object>
.
However, Task<TResult>
IS NOT covariant. That means you can't return a more specific object in place of a generic definition (if you've Task<object>
, you can't return Task<string>
).
Now, adding async
and await
makes the compiler treat this a bit differently. Simplifying, async
cancels out the Task
declaration, so it returns the TResult
. And if you have a method object Foo()
, you can use return ""
inside this method. Because the return type of a method is always covariant.
To summarize, async Task<TResult>
is similar of TResult
, so you can return more specific objects this way. However, Task<TResult>
is a non-covariant type, so you have to return exactly the type specified.
CodePudding user response:
Task.WhenAll<T>
returns a Task<T[]>
, so in your case
Task<IEnumerable<Decsriptor>[]> allDescriptors = Task.WhenAll(GetMovieDescriptorsAsync, GetSongDescriptorsAsync);
allDescriptors
is a Task<IEnumerable<Decsriptor>[]>
, that is a Task wrapping an array of Enumerables of Descriptor.
First order of business would be getting the task result:
IEnumerable<Descriptor>[] taskResults = allDescriptors.Result;
Then merging each IEnumerable<Descriptor>
in the array taskResults
into a flat list:
IEnumerable<Descriptor> descriptors = taskResults.SelectMany(value => value);