为什么async/await允许从List到IEnumerable的隐式转换?

And*_*Gis 19 .net c# asynchronous

我一直在玩async/await,发现了一些有趣的东西.看看下面的例子:

// 1) ok - obvious
public Task<IEnumerable<DoctorDto>> GetAll()
{
    IEnumerable<DoctorDto> doctors = new List<DoctorDto>
    {
        new DoctorDto()
    };

    return Task.FromResult(doctors);
}

// 2) ok - obvious
public async Task<IEnumerable<DoctorDto>> GetAll()
{
    IEnumerable<DoctorDto> doctors = new List<DoctorDto>
    {
        new DoctorDto()
    };

    return await Task.FromResult(doctors);
}

// 3) ok - not so obvious
public async Task<IEnumerable<DoctorDto>> GetAll()
{
    List<DoctorDto> doctors = new List<DoctorDto>
    {
        new DoctorDto()
    };

    return await Task.FromResult(doctors);
}

// 4) !! failed to build !!
public Task<IEnumerable<DoctorDto>> GetAll()
{
    List<DoctorDto> doctors = new List<DoctorDto>
    {
        new DoctorDto()
    };

    return Task.FromResult(doctors);
}
Run Code Online (Sandbox Code Playgroud)

考虑案例3和4.唯一的区别是3使用async/await关键字.3构建正常,但是4给出了关于将List隐式转换为IEnumerable的错误:

Cannot implicitly convert type 
'System.Threading.Tasks.Task<System.Collections.Generic.List<EstomedRegistration.Business.ApiCommunication.DoctorDto>>' to 
'System.Threading.Tasks.Task<System.Collections.Generic.IEnumerable<EstomedRegistration.Business.ApiCommunication.DoctorDto>>'  
Run Code Online (Sandbox Code Playgroud)

什么是async/await关键字在这里改变了?

avo*_*avo 25

Task<T> 根本不是协变型.

虽然List<T>可以转换为IEnumerable<T>,但Task<List<T>>转换为Task<IEnumerable<T>>.在#4中,Task.FromResult(doctors)返回Task<List<DoctorDto>>.

在#3中,我们有:

return await Task.FromResult(doctors)
Run Code Online (Sandbox Code Playgroud)

这与以下相同:

return await Task<List<DoctorDto>>.FromResult(doctors)
Run Code Online (Sandbox Code Playgroud)

这与以下相同:

List<DoctorDto> result = await Task<List<DoctorDto>>.FromResult(doctors);
return result;
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为List<DoctorDto>可以转换IEnumerable<DoctorDto>.

  • 但事实是,如果您添加async / await关键字,则可以将其突然转换。 (2认同)
  • 这显然解释了为什么第四个例子失败了,但问题实际上是为什么第三个例子*通过*.有关async/await生成代码的说明是必需的. (2认同)

Ste*_*ary 14

想想你的类型.Task<T>不是变体,所以它不可转换Task<U>,即使T : U.

但是,如果tTask<T>,那么类型await tT,并且T可以转换为Uif T : U.


Ada*_*rth 7

很明显,您明白为什么List<T>至少可以返回为IEnumerable<T>: 仅仅因为它实现了该接口。

同样很明显,第三个示例正在做一些“额外”的事情,而第四个示例则没有。正如其他人所说,第 4 次失败是因为缺乏协方差(或者相反,我永远不记得他们走了哪条路!),因为您直接试图提供一个实例 Task<List<DoctorDto>>作为Task<IEnumerable<DoctorDto>>.

第三遍的原因是因为await添加了大量“支持代码”以使其按预期工作。这段代码解析Task<T>T,这样return await Task<something>将返回泛型中关闭的类型Task<T>,在这种情况下something

方法签名然后返回Task<T>并且它再次工作由编译器解决,它需要Task<T>, Task, 或void对于异步方法,并简单地将您的T背部按摩为 aTask<T>作为所有背景生成的异步/等待延续gubbins的一部分。

正是这个额外的步骤,Tawait并需要将它转换一个Task<T>给它工作所需的空间。您不是试图采用 a 的现有实例Task<U>来满足 a Task<T>,而是创建一个全新的Task<T>,给它 a U : T,并且在构造时,隐式强制转换如您所料发生(与您期望IEnumerable<T> myVar = new List<T>();的工作方式完全相同) .

责备/感谢编译器,我经常这样做;-)