Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is Task<T> not co-variant?

class ResultBase {}
class Result : ResultBase {}

Task<ResultBase> GetResult() {
    return Task.FromResult(new Result());
}

The compiler tells me that it cannot implicitly convert Task<Result> to Task<ResultBase>. Can someone explain why this is? I would have expected co-variance to enable me to write the code in this way.

like image 623
chosenbreed37 Avatar asked Jun 23 '15 07:06

chosenbreed37


3 Answers

According to someone who may be in the know...

The justification is that the advantage of covariance is outweighed by the disadvantage of clutter (i.e. everyone would have to make a decision about whether to use Task or ITask in every single place in their code).

It sounds to me like there is not a very compelling motivation either way. ITask<out T> would require a lot of new overloads, probably quite a bit under the hood (I cannot attest to how the actual base class is implemented or how special it is compared to a naive implementation) but way more in the form of these linq-like extension methods.

Somebody else made a good point - the time would be better spent making classes covariant and contravariant. I don't know how hard that would be, but that sounds like a better use of time to me.

On the other hand, somebody mentioned that it would be very cool to have a real yield return like feature available in an async method. I mean, without sleight of hand.

like image 195
tacos_tacos_tacos Avatar answered Sep 21 '22 23:09

tacos_tacos_tacos


I realize I'm late to the party, but here's an extension method I've been using to account for this missing feature:

/// <summary>
/// Casts the result type of the input task as if it were covariant
/// </summary>
/// <typeparam name="T">The original result type of the task</typeparam>
/// <typeparam name="TResult">The covariant type to return</typeparam>
/// <param name="task">The target task to cast</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static async Task<TResult> AsTask<T, TResult>(this Task<T> task) 
    where T : TResult 
    where TResult : class
{
    return await task;
}

This way you can just do:

class ResultBase {}
class Result : ResultBase {}

Task<Result> GetResultAsync() => ...; // Some async code that returns Result

Task<ResultBase> GetResultBaseAsync() 
{
    return GetResultAsync().AsTask<Result, ResultBase>();
}
like image 24
Sergio0694 Avatar answered Sep 20 '22 23:09

Sergio0694


I've had success with the MorseCode.ITask NuGet package. At this point it's pretty stable (no updates in a few years) but it was trivial to install and the only thing you needed to do to translate from an ITask to a Task was call .AsTask() (and the reverse extension method also ships with the package).

like image 1
ssmith Avatar answered Sep 21 '22 23:09

ssmith