'Covariance and contravariance on Tasks
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>
Solution 1:[1]
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.
Solution 2:[2]
If you can use .NET Core or .NET 5 and onwards the solution is to use IAsyncEnumerable<T> instead of Task<IEnumerable<T>>.
The covariant interface would look like:
public interface IEntityRepository<out T> : IRepository<IEntity> {
void RequeryDataBase();
IEnumerable<T> Search(string pattern);
IAsyncEnumerable<T> SearchAsync(string pattern);
SearchContext Context { get; }
string BaseTableName { get; }
}
More info about covariant async can be found here.
Solution 3:[3]
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
Solution 4:[4]
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.
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 | Zoran Horvat |
| Solution 2 | Theodor Zoulias |
| Solution 3 | IluTov |
| Solution 4 | IluTov |
