为什么任务<;T>;不是共同变体

本文关键字:gt 任务 lt 为什么 | 更新日期: 2023-09-27 17:59:10

class ResultBase {}
class Result : ResultBase {}
Task<ResultBase> GetResult() {
    return Task.FromResult(new Result());
}

编译器告诉我它不能隐式地将Task<Result>转换为Task<ResultBase>。有人能解释一下为什么会这样吗?我本来希望协变能够使我以这种方式编写代码。

为什么任务<;T>;不是共同变体

据知情人士透露。。。

理由是协方差的优势被杂乱的缺点(即每个人都必须决定是否在每一个中使用Task或ITask放置在他们的代码中(。

在我看来,无论哪种方式,都没有什么令人信服的动机。ITask<out T>将需要很多新的重载,可能有点隐蔽(我无法证明实际基类是如何实现的,也无法证明它与简单的实现相比有多特别(,但更多的是以这些类似linq的扩展方法的形式。

其他人提出了一个很好的观点——时间最好花在class的协变和反变上。我不知道这会有多难,但对我来说,这听起来更能利用时间

另一方面,有人提到,在async方法中有一个真正的类似yield return的功能是非常酷的。我的意思是,没有花招。

我意识到我参加聚会迟到了,但这里有一个我一直在使用的扩展方法来解释这个缺失的功能:

/// <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;
}

这样你就可以做到:

class ResultBase {}
class Result : ResultBase {}
Task<Result> GetResultAsync() => ...; // Some async code that returns Result
Task<ResultBase> GetResultBaseAsync() 
{
    return GetResultAsync().AsTask<Result, ResultBase>();
}

我在MorseCode.ITask NuGet包方面取得了成功。目前它相当稳定(几年内没有更新(,但安装起来很简单,从ITask转换为Task只需要调用.AsTask()(反向扩展方法也随包提供(。

在我的例子中,我在编译时不知道Task的泛型参数,必须使用System.Threading.Tasks.Task基类。这是我根据上面的例子创建的解决方案,也许会对某人有所帮助。

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static async Task<T> AsTask<T>(this Task task)
    {
        var taskType = task.GetType();
        await task;
        return (T)taskType.GetProperty("Result").GetValue(task);
    }