'Cannot convert type 'Task<Derived>' to 'Task<Interface>'

I have the following function with a delegate parameter that accepts a type of one interface and returns a task of another.

public void Bar(Func<IMessage, Task<IResult>> func)
{
    throw new NotImplementedException();
}

I also have a function with a parameter as an instance of IMessage and returns a Task. Message and Result are implementations of IMessage and IResult respectively.

private Task<Result> DoSomething(Message m) { return new Task<Result>(() => new Result()); }

I receive an error when I pass DoSomething into Bar.

Bar(m => DoSomething((Message)m));
// Cannot convert type 'Task<Result>' to 'Task<IResult>'

Why won't Result implicitly convert into IResult?

I would imagine it's an issue with covariance. However, in this case, Result implements IResult. I've also tried to solve the covariance issue by creating an interface and marking TResult as covariant.

public interface IFoo<TMessage, out TResult>
{
    void Bar(Func<TMessage, Task<TResult>> func);
}

But I get the error:

Invalid variance: The type parameter 'TResult' must be invariantly valid on IFoo<TMessage, TResult>.Bar(Func<TMessage, Task<TResult>>). 'TResult' is covariant.

Now I'm stuck. I know I have an issue with covariance but I'm not sure how to solve it. Any ideas?

Edit: This question is specific to Tasks. I ran into this problem by implementing async await in my application. I came across this generic implementation and added a Task. Others may have the same issues during this type of conversion.

Solution: Here's the solution based on the answers below:

Func<Task<Result>, Task<IResult>> convert = async m => await m;
Bar(m => convert(DoSomething((Message)m)));


Solution 1:[1]

It seems like there's got to be a cleaner way of doing this, but it is possible to create a wrapping task of the correct type. I introduced a new function called GeneralizeTask().

Task<TBase> GeneralizeTask<TBase, TDerived>(Task<TDerived> task) 
    where TDerived : TBase 
{
    var newTask = new Task<TBase>(() => {
        if (task.Status == TaskStatus.Created) task.Start();
        task.Wait();
        return (TBase)task.Result;
    });
    return newTask;
}

Edit:

As @EricLippert points out, this can be simplified significantly. I first tried to find such a way to implement this method, but couldn't find one that compiled. As it turned out, the real solution was even simpler than I imagined.

async Task<TBase> GeneralizeTask<TBase, TDerived>(Task<TDerived> task) 
    where TDerived : TBase 
{
    return (TBase) await task;
}

You can then invoke Bar() like this.

Bar(m => GeneralizeTask<IResult, Result>(DoSomething((Message)m)));

Solution 2:[2]

I am using another version of GeneralizeTask, which is stated by @Recursive, on Asp Net Core Framework. Here it is:

public static Task<TBase> GeneralizeTask<TDerived, TBase>(this Task<TDerived> task, CancellationToken cancellationToken = default) 
 where TBase : class 
 where TDerived : TBase
{
    var result = task.ContinueWith(t => (TBase)t.Result, cancellationToken);
    return result;
}

Solution 3:[3]

As Eric mentioned, C# does not allow generic variance on classes. I suggest a simple pair of extension methods for the two kinds of Task:

public static class MiscExtensionMethods
{
    public static async Task<TBase> Upcast<TDerived, TBase>(this Task<TDerived> task)
          where TBase : class
          where TDerived : TBase
          => await task;
    public static async ValueTask<TBase> Upcast<TDerived, TBase>(this ValueTask<TDerived> task)
          where TBase : class
          where TDerived : TBase
          => await task;
}

Unfortunately you'll have to specify both type parameters, as per C# rules, and the #nullability checker doesn't like these methods if nullable types are involved.

// example (causes nullability warning)
return GetListAsync(...).Upcast<List<Thing>?, IReadOnlyList<Thing>?>();

// example (suppresses nullability warning)
return GetListAsync(...).Upcast<List<Thing>, IReadOnlyList<Thing>>()!;

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3 Qwertie