为什么Task <T>不是共变体?

cho*_*d37 50 c# task covariance

class ResultBase {}
class Result : ResultBase {}

Task<ResultBase> GetResult() {
    return Task.FromResult(new Result());
}
Run Code Online (Sandbox Code Playgroud)

编译器告诉我它不能隐式转换Task<Result>Task<ResultBase>.有人可以解释为什么会这样吗?我希望协方差可以让我以这种方式编写代码.

tac*_*cos 21

根据可能知道的人 ...

理由是混淆的缺点超过了协方差的优势(即每个人都必须决定是否在代码中的每个地方使用Task或ITask).

听起来对我来说,无论哪种方式都没有非常引人注目的动机. ITask<out T>会需要很多新的重载,可能还有很多新的重载(我无法证明实际的基类是如何实现的,或者它与一个简单的实现相比有多特别)但更多的是以类似这样linq的扩展方法的形式.

其他人提出了一个很好的观点 - 时间会更好地用于class协变和逆变.我不知道会有多难,但这对我来说听起来更好用.

另一方面,有人提到yield returnasync方法中提供真实的功能会非常酷.我的意思是,没有狡猾的手.

  • `async`,`await`依赖于一个合适的`GetAwaiter`方法的存在,所以它已经与`Task`类分离了. (6认同)
  • @FranciscoNeto 这根本不是真的。`async`/`await` 仅依赖于 `GetAwaiter` 方法的存在,该方法返回具有适当签名的 `IsCompleted`、`OnCompleted` 和 `GetResult` 成员的对象。因此,可等待的自定义实现是可能的。[参见此处](https://codeblog.jonskeet.uk/2011/05/13/eduasync-part-3-the-shape-of-the-async-method-awaitable-boundary/) (4认同)

Ser*_*694 9

我意识到我迟到了,但这是我用来解释这个缺失功能​​的扩展方法:

/// <summary>
/// Casts the result type of the input task as if it were covariant
/// </summary>
/// <typeparam name="T">The original result type of the task</typeparam>
/// <typeparam name="TResult">The covariant type to return</typeparam>
/// <param name="task">The target task to cast</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static async Task<TResult> AsTask<T, TResult>(this Task<T> task) 
    where T : TResult 
    where TResult : class
{
    return await task;
}
Run Code Online (Sandbox Code Playgroud)

这样你就可以这样做:

class ResultBase {}
class Result : ResultBase {}

Task<Result> GetResultAsync() => ...; // Some async code that returns Result

Task<ResultBase> GetResultBaseAsync() 
{
    return GetResultAsync().AsTask<Result, ResultBase>();
}
Run Code Online (Sandbox Code Playgroud)

  • 一些额外的建议:如果你只是想要向下转换,就像在例子中一样,这也适用:`Task.FromResult <ResultBase>(new Result());` (6认同)
  • 实际上,后一种评论应该是公认的答案,因为整个问题与协方差无关,而是框架已经支持的情况。 (2认同)
  • 此代码正在创建额外的异步状态机,这将对性能产生影响。这仍然不是理想的解决方案。我想知道是否有一个理想的。 (2认同)