无法将类型’Task ‘转换为’Task ‘

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

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

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

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

当我将DoSomething传递到Bar时,我收到错误。

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

为什么Result不会隐式转换为IResult

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

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

但我得到错误:

方差无效:类型参数’TResult’必须在IFoo.Bar(Func<TMessage, Task>) 。 ‘TResult’是协变的。

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

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

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

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

C#不允许对类进行变化,只允许使用引用类型参数化的接口和委托。 Task是一个类。

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

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

似乎必须有一种更简洁的方法,但是可以创建一个正确类型的包装任务。 我介绍了一个名为GeneralizeTask()的新函数。

 Task GeneralizeTask(Task task) where TDerived : TBase { var newTask = new Task(() => { if (task.Status == TaskStatus.Created) task.Start(); task.Wait(); return (TBase)task.Result; }); return newTask; } 

编辑:

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

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

然后,您可以像这样调用Bar()

 Bar(m => GeneralizeTask(DoSomething((Message)m)));