任务的协方差和逆变性
本文关键字:方差 任务 | 更新日期: 2023-09-27 17:54:11
给出下面的代码片段,我不太明白为什么我要实现的是不可能的:
接口:
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; }
}
在IRepository<IEntity>
中只是简单的通用CRUD定义。
我在这行得到一个错误:Task<IEnumerable<T>> SearchAsync(string pattern);
方法返回类型必须是输出安全的。无效方差:类型参数T必须在Task
上始终有效。
请帮助我理解,为什么我不能使用<out T>
与Task<T>
Task<T>
不是协变的。方差只能应用于泛型接口(和委托,但这与这里无关)。
。Task<IEnumerable<Dog>>
不能赋值给Task<IEnumerable<Animal>>
。因此,你的接口也不能被标记为协变。
你可能想看看这个相关的问题
在决定泛型接口中泛型类型参数的方差时,必须考虑接口中泛型类型参数的所有使用。每次使用都可能引入一些关于方差的约束。这包括:
- 用作方法的输入参数-禁止协方差
- 用作方法的返回值-禁止逆向
- 作为其他通用衍生的一部分使用,如
Task<T>
-此类使用可能不允许协方差,不允许逆变,或两者兼有
我使用否定逻辑来强调所有这些情况基本上都是引入约束。在分析之后,您将知道是否有任何元素不允许协方差,然后您的参数可能不会被声明为out
。相反,如果任何元素具有不允许的逆变性,则可能不会将参数声明为in
。
在您的特殊情况下,第一个Search
方法返回IEnumerable<T>
,使得逆变不适用。但是,SearchAsync返回Task<IEnumerable<T>>
,并且这种用法引入了Task类型中存在的约束—它是不变的,这意味着这次out
也不可能。
结果是你的泛型接口必须在它的泛型参数类型上是不变的,以便满足它所有方法的签名。
如果你可以使用。net Core或。net 5及以后的版本,解决方案是使用IAsyncEnumerable<T>
而不是Task<IEnumerable<T>>
。
协变接口看起来像:
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; }
}
关于协变异步的更多信息可以在这里找到
你可以使用
IObservable<out T>
几乎与Task<T>
相同,但具有协变类型。当你需要的时候,它总是可以被转换成一个任务。您在代码的可读性上损失了一点,因为Task<T>
假定(并强制)您只能获得一个结果,而IObservable<T>
可以获得多个结果。但是你可以做
IObservable<T> o = ...
var foo = await o;
与
相同Task<T> t = ...
var foo = await t;
注意,您需要从Rx库中包含System.Reactive.Linq
名称空间才能使此工作。它将为IObservable<>
添加一个扩展方法,使其可等待。
如前所述,Task<TResult>
中的TResult
不是协变的。你可以用ContinueWith
:
Task
实例var intTask = Task.FromResult(42);
var objectTask = intTask.ContinueWith(t => (object) t.Result);
await objectTask; // 42