11
votes

Given the followin snippet, i quite dont understand WHY what im going to achieve is not possible:

Interface:

public interface IEntityRepository<out T> : IRepository<IEntity> {

    void RequeryDataBase();

    IEnumerable<T> Search(string pattern);

    Task<IEnumerable<T>> SearchAsync(string pattern);

    SearchContext Context { get; }

    string BaseTableName { get; }
  }

In IRepository<IEntity> are just simple generic CRUD defined.

I get an Error on this line: Task<IEnumerable<T>> SearchAsync(string pattern);

Error:

method return type must be output safe. invalid variance: the type parameter T must be invariantly valid on Task

Please help me understand, why i cant use <out T> with a Task<T>

4
I think the result of the operation needs to be assigned to the task, making it an <in T> - Jim
@Jim but the result type is iEnumerable<T>, not just T....I would intuitivly think that this should work. - René Vogt
Task<T> is a class so it's invariant. - Lee
@RenéVogt I thought that too. The Task itself shouldnt care about, what comes back. - lokusking

4 Answers

16
votes

Task<T> is not covariant. Variance can only be applied to generic interfaces (and delegates, but that's not relevant here).

e.g. Task<IEnumerable<Dog>> cannot be assigned to Task<IEnumerable<Animal>>. Because of this, your interface cannot be marked as covariant either.

You might want to see this related question.

5
votes

When deciding on variance of a generic type argument in some generic interface, you have to take into account all uses of the generic type argument inside the interface. Each use may introduce some constraints regarding variance. This includes:

  • Uses as input argument to a method - disallows covariance
  • Uses as return value from a method - disallows contravariance
  • Uses as part of other generic derivation, such as Task<T> - such use may disallow covariance, disallow contravariance, or both

I was using negative logic to emphasize that all these cases are basically introducing constraints. After the analysis, you will know whether any element has disallowed covariance and then your argument might not be declared as out. Conversely, if any element has disallowed contravariance, your argument might not be declared as in.

In your particular case, the first Search method returns IEnumerable<T>, rendering contravariance non-applicable. But then, SearchAsync is returning Task<IEnumerable<T>>, and that usage introduces constraints that exist in the Task type - it is invariant, meaning that this time out is impossible as well.

The result is that your generic interface must be invariant on its generic argument type in order to satisfy signatures of all its methods.

0
votes

As others have mentioned before, TResult in Task<TResult> is not covariant. What you can do however is create a new Task instance using ContinueWith:

var intTask = Task.FromResult(42);
var objectTask = intTask.ContinueWith(t => (object) t.Result);

await objectTask; // 42
-1
votes

You can cheat and use

IObservable<out T>

which is almost the same as Task<T> but with a covariant type. It can always be converted to a task when you need it. You lose a little bit in the readability of the code because it is assumed ( and enforced ) with Task<T> that you only get one result but with IObservable<T> you can get many. However you can do

IObservable<T> o = ...
var foo = await o;

just the same as

Task<T> t = ...
var foo = await t;

Note that you'll need to include the System.Reactive.Linq namespace from the Rx library to make this work. It will add an extension method to IObservable<> that will make it awaitable.