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

时间:2015-06-23 07:54:59

标签: c# task covariance

class ResultBase {}
class Result : ResultBase {}

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

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

4 个答案:

答案 0 :(得分:21)

根据someone who may be in the know ...

  

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

对我来说,无论如何都没有非常令人信服的动机。 ITask<out T>需要很多新的重载,可能需要相当多的重载(我无法证明实际的基类是如何实现的,或者它与一个简单的实现相比有多特别)但是更多的形式是这些linq - 类似的扩展方法。

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

另一方面,有人提到在yield return方法中提供真正的async类似功能会非常酷。我的意思是,没有狡猾的手。

答案 1 :(得分: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 Task<TResult> AsTask<T, TResult>(this Task<T> task) 
    where T : TResult 
    where TResult : class
{
    return task.ContinueWith(t => t.Result as TResult);
}

这样你就可以:

class ResultBase {}
class Result : ResultBase {}

Task<ResultBase> GetResult() 
{
    return Task.FromResult(new Result()).AsTask<Result, ResultBase>();
}

答案 2 :(得分:0)

在我的情况下,我在编译时不了解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);
    }

答案 3 :(得分:0)

我在 MorseCode.ITask NuGet package 上取得了成功。在这一点上它非常稳定(几年内没有更新)但是安装很简单,从 ITask 转换为 Task 唯一需要做的就是调用 .AsTask()(以及反向扩展方法也随包裹一起提供)。