无法将类型'Task <Derived>'转换为'Task <Interface>'

Nae*_*ael 16 c#

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

public void Bar(Func<IMessage, Task<IResult>> func)
{
    throw new NotImplementedException();
}
Run Code Online (Sandbox Code Playgroud)

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

private Task<Result> DoSomething(Message m) { return new Task<Result>(() => new Result()); }
Run Code Online (Sandbox Code Playgroud)

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

Bar(m => DoSomething((Message)m));
// Cannot convert type 'Task<Result>' to 'Task<IResult>'
Run Code Online (Sandbox Code Playgroud)

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

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

public interface IFoo<TMessage, out TResult>
{
    void Bar(Func<TMessage, Task<TResult>> func);
}
Run Code Online (Sandbox Code Playgroud)

但我得到错误:

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

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

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

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

Func<Task<Result>, Task<IResult>> convert = async m => await m;
Bar(m => convert(DoSomething((Message)m)));
Run Code Online (Sandbox Code Playgroud)

Eri*_*ert 25

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

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

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


rec*_*ive 5

好像还有的是这样做的更清洁的方式,但它可以创建正确类型的包装任务。我介绍了一个名为的新函数GeneralizeTask()

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;
}
Run Code Online (Sandbox Code Playgroud)

编辑:

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

async Task<TBase> GeneralizeTask<TBase, TDerived>(Task<TDerived> task) 
    where TDerived : TBase 
{
    return (TBase) await task;
}
Run Code Online (Sandbox Code Playgroud)

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

Bar(m => GeneralizeTask<IResult, Result>(DoSomething((Message)m)));
Run Code Online (Sandbox Code Playgroud)

  • 有一些原因导致您不仅仅使用`await`吗?似乎`async Task &lt;B&gt; Cast &lt;B,D&gt;(Task &lt;D&gt; t),其中D:B =&gt;(B)等待t;`要简单得多。 (4认同)