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

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

// 1) ok - obvious public Task<IEnumerable> GetAll() { IEnumerable doctors = new List { new DoctorDto() }; return Task.FromResult(doctors); } // 2) ok - obvious public async Task<IEnumerable> GetAll() { IEnumerable doctors = new List { new DoctorDto() }; return await Task.FromResult(doctors); } // 3) ok - not so obvious public async Task<IEnumerable> GetAll() { List doctors = new List { new DoctorDto() }; return await Task.FromResult(doctors); } // 4) !! failed to build !! public Task<IEnumerable> GetAll() { List doctors = new List { new DoctorDto() }; return Task.FromResult(doctors); } 

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

 Cannot implicitly convert type 'System.Threading.Tasks.Task<System.Collections.Generic.List>' to 'System.Threading.Tasks.Task<System.Collections.Generic.IEnumerable>' 

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

Task根本不是协变类型。

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

在#3中,我们有:

 return await Task.FromResult(doctors) 

这与:

 return await Task>.FromResult(doctors) 

这与:

 List result = await Task>.FromResult(doctors); return result; 

这是有效的,因为List可以转换为IEnumerable

想想你的类型。 Task不是变体,因此它不能转换为Task ,即使T : U

但是,如果tTask ,则await t的类型为T ,如果T : UT : U可以转换为U

很明显你明白为什么List至少可以作为IEnumerable返回:只是因为它实现了那个接口。

同样清楚的是,第三个例子是做一些“额外”的事情,而第四个例子则没有。 正如其他人所说,第四次失败是因为缺乏协方差(或者反之,我永远不会记得他们Task> !),因为你直接试图提供一个Task>的实例作为Task>一个实例。

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

然后方法签名返回Task并且它的工作原理再次由编译器解决,这需要TaskTaskvoid for async方法,并简单地将你的T按摩回到Task作为所有的一部分背景生成asyn / await continuation gubbins。

这是一个额外的步骤,从await中获取T并需要将其转换 Task ,从而为其提供所需的空间。 您不是试图使用Task的现有实例来满足Task ,而是创建一个全新的Task ,给它一个U : T ,并且在构造时隐式转换发生为你会期望(以与你期望的完全相同的方式IEnumerable myVar = new List();来工作)。

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