24

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)));
Nael
  • 1,479
  • 1
  • 14
  • 20
  • 6
    Classes are invariant. `Task` and `Task` when `T1!=T2` are different types. – user4003407 Jun 14 '16 at 17:38
  • 2
    `async m => (IResult)await DoSomething((Message)m).ConfigureAwait(false)` – user4003407 Jun 14 '16 at 17:50
  • 2
    You could make your question way simpler: why cannot I set a variable of type `Task` with an instance of `Task` and save many people time reading your question and getting through it. – Zverev Evgeniy Jun 14 '16 at 18:18
  • Possible duplicate of [Covariance for generic classes](http://stackoverflow.com/questions/31797943/covariance-for-generic-classes) – Zverev Evgeniy Jun 14 '16 at 18:30
  • Related: [How to convert a Task to a Task?](https://stackoverflow.com/questions/15530099/how-to-convert-a-tasktderived-to-a-tasktbase) – Theodor Zoulias Oct 09 '22 at 18:43

4 Answers4

33

C# does not allow variance on classes, only interfaces and delegates that are parameterized with reference types. Task<T> is a class.

This is somewhat unfortunate, as Task<T> is one of those rare classes that could be made safely covariant.

However it is easy enough to convert a Task<Derived> to a Task<Base>. Just make a helper method / lambda that takes a Task<Derived> and returns Task<Base>, await the passed-in task, and return the value cast to Base. The C# compiler will take care of the rest. Of course you lose referential identity, but you weren't ever going to get that with a class.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
6

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)));
recursive
  • 83,943
  • 34
  • 151
  • 241
  • 1
    This is needlessly allocating a thread pool thread to synchronously sit there waiting on the composed task to complete, you're also returning an unstarted task, so it'll never finish (which is exactly why you shouldn't be using the `Task` constructor at all, ever), you're also not properly handling error/cancellation cases for the composed task. – Servy Jun 14 '16 at 20:11
  • 6
    Is there some reason why you're not simply using `await`? It seems like `async Task Cast(Task t) where D : B => (B) await t;` is a lot simpler. – Eric Lippert Jun 14 '16 at 22:19
  • @EricLippert: That's pretty clearly a better solution. I did try to use await, but couldn't figure out how to write it. All my attempts yielded un-compilable code with errors like `CS4010 Cannot convert async lambda expression to delegate type 'Func'. An async lambda expression may return void, Task or Task, none of which are convertible to 'Func'.`. Apparently I don't understand tasks as well as I thought I did. – recursive Jun 15 '16 at 00:02
  • 1
    I think you meant to say `Func>` What is a lambda? a function. What does an asynchronous function return? A task. What value does the task produce? A TBase. So that's a function of task of tbase. – Eric Lippert Jun 15 '16 at 16:36
1

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;
}
Derviş Kayımbaşıoğlu
  • 28,492
  • 4
  • 50
  • 72
  • 1
    I'd suggest using `(TBase)t.Result` instead of `t.Result as TBase`. It'll perform slightly better, and semantically emphasizes the point that you are fully expecting TDerived to be safely castable to TBase. – StriplingWarrior Feb 21 '19 at 21:52
0

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>>()!;
Qwertie
  • 16,354
  • 20
  • 105
  • 148