无法转换类型&#39;任务<衍生>&#39;到&#39;任务<interface>&#39;

时间:2016-06-14 17:35:02

标签: c#

我有一个委托参数的以下函数,该参数接受一个接口的类型并返回另一个接口的任务。

public void Bar(Func<IMessage, Task<IResult>> func)
{
    throw new NotImplementedException();
}

我还有一个带参数的函数作为IMessage的实例并返回一个Task。 MessageResult分别是IMessageIResult的实现。

private Task<Result> DoSomething(Message m) { return new Task<Result>(() => new Result()); }

我将DoSomething传入Bar时收到错误。

Bar(m => DoSomething((Message)m));
// Cannot convert type 'Task<Result>' to 'Task<IResult>'

为什么赢得Result隐式转换为IResult

我认为它是协方差的问题。但是,在这种情况下,Result会实现IResult。我还试图通过创建一个接口并将TResult标记为协变来解决协方差问题。

public interface IFoo<TMessage, out TResult>
{
    void Bar(Func<TMessage, Task<TResult>> func);
}

但我收到错误:

  

无效方差:类型参数&#39; TResult&#39;必须是不变的   在IFoo<TMessage, TResult>.Bar(Func<TMessage, Task<TResult>>)上有效。   &#39; TResult&#39;是协变的。

现在我被卡住了。我知道我有协方差的问题,但我不知道如何解决它。有什么想法吗?

编辑:此问题特定于任务。我在我的应用程序中实现async await遇到了这个问题。我遇到了这个通用实现,并添加了Task。在此类转换过程中,其他人可能会遇到相同的问题。

解决方案:以下是基于以下答案的解决方案:

Func<Task<Result>, Task<IResult>> convert = async m => await m;
Bar(m => convert(DoSomething((Message)m)));

3 个答案:

答案 0 :(得分:24)

C#不允许类的变化,只允许使用引用类型参数化的接口和委托。 Task<T>是一个班级。

这有点不幸,因为Task<T>可以安全协变的罕见类之一。

然而,将Task<Derived>转换为Task<Base>很容易。只需创建一个带有Task<Derived>并返回Task<Base>的辅助方法/ lambda,等待传入的任务,并将值转换为Base。 C#编译器将负责其余的工作。当然,你失去了参考身份,但你不会在课堂上得到它。

答案 1 :(得分:3)

似乎 是一种更干净的方法,但是可以创建正确类型的包装任务。我介绍了一个名为Task<TBase> GeneralizeTask<TBase, TDerived>(Task<TDerived> task) where TDerived : TBase { var newTask = new Task<TBase>(() => { if (task.Status == TaskStatus.Created) task.Start(); task.Wait(); return (TBase)task.Result; }); return newTask; } 的新函数。

async Task<TBase> GeneralizeTask<TBase, TDerived>(Task<TDerived> task) 
    where TDerived : TBase 
{
    return (TBase) await task;
}

编辑:

正如@EricLippert指出的那样,这可以大大简化。我首先尝试找到这种方法来实现这个方法,但是找不到编译的方法。事实证明,真正的解决方案甚至比我想象的还要简单。

Bar()

然后,您可以像这样调用Bar(m => GeneralizeTask<IResult, Result>(DoSomething((Message)m)));

{{1}}

答案 2 :(得分:0)

我正在使用Asp Net Core框架上的@Recursive声明的GeneralizeTask的另一个版本。在这里:

public static Task<TBase> GeneralizeTask<TDerived, TBase>(this Task<TDerived> task, CancellationToken cancellationToken = default) 
 where TBase : class 
 where TDerived : TBase
{
    var result = task.ContinueWith(t => (TBase)t.Result, cancellationToken);
    return result;
}