Why is Task<T> not co-variant?

C#TaskCovariance

C# Problem Overview


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.

C# Solutions


Solution 1 - C#

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.

Solution 2 - C#

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>();
}

Solution 3 - C#

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).

Solution 4 - C#

In my case I didn't know Task generic argument in compile time and had to work with System.Threading.Tasks.Task base class. Here is my solution created from the example above, perhaps will help someone.

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static async Task<T> AsTask<T>(this Task task)
    {
        var taskType = task.GetType();
        await task;
        return (T)taskType.GetProperty("Result").GetValue(task);
    }

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionchosenbreed37View Question on Stackoverflow
Solution 1 - C#tacos_tacos_tacosView Answer on Stackoverflow
Solution 2 - C#Sergio0694View Answer on Stackoverflow
Solution 3 - C#ssmithView Answer on Stackoverflow
Solution 4 - C#Oleg BevzView Answer on Stackoverflow