下面的函数带有一个委托参数,该参数接受一个接口的类型,并返回另一个接口的任务。
public void Bar(Func<IMessage, Task<IResult>> func)
{
throw new NotImplementedException();
}我还有一个带有参数的函数,作为IMessage的一个实例,并返回一个任务。Message和Result分别是IMessage和IResult的实现。
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);
}但我知道错误是:
无效方差:类型参数'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)));发布于 2016-06-14 17:52:41
C#不允许类上的差异,只允许使用引用类型参数化的接口和委托。Task<T>是一个类。
这有点不幸,因为Task<T>是可以安全地进行协变的罕见类之一。
但是,很容易将Task<Derived>转换为Task<Base>。只需创建一个接受Task<Derived>并返回Task<Base>的助手方法/ lambda,等待传入的任务,并将值转换为Base。C#编译器将处理其余部分。当然,你失去了引用的身份,但你永远不会得到一个类。
发布于 2016-06-14 18:07:33
似乎必须有一种更干净的方法来完成这个任务,但是创建正确类型的包装任务是可能的。我引入了一个名为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;
}编辑:
正如@EricLippert所指出的,这可以大大简化。我最初试图找到一种实现此方法的方法,但没有找到编译的方法。事实证明,真正的解决办法比我想象的还要简单。
async Task<TBase> GeneralizeTask<TBase, TDerived>(Task<TDerived> task)
where TDerived : TBase
{
return (TBase) await task;
}然后您可以像这样调用Bar()。
Bar(m => GeneralizeTask<IResult, Result>(DoSomething((Message)m)));发布于 2019-02-21 21:40:26
我正在使用另一个版本的GeneralizeTask,它是由@Recursive,在上声明的。下面是:
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;
}https://stackoverflow.com/questions/37818642
复制相似问题