【问题标题】:Why async / await allows for implicit conversion from a List to IEnumerable?为什么 async / await 允许从 List 到 IEnumerable 的隐式转换?
【发布时间】:2020-04-24 16:52:17
【问题描述】:

我一直在玩 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);
}

考虑情况 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>>'  

这里的 async/await 关键字发生了什么变化?

【问题讨论】:

  • 1和4有什么区别?我是不是瞎了眼了?
  • 基于问题(编译器输出)我认为实际代码中的 4 有 List&lt;DoctorDto&gt; doctors = new ... 对吗? ...是的
  • @CarstenKönig 你是对的 - 更正它
  • @Hammerstein 我的错应该是 List 而不是 IEnumerable,我刚刚纠正了它。
  • @gisek 原因很简单——awaitTask&lt;List&lt;DoctorDto&gt;&gt; 中的值解包,因此得到List&lt;DoctorDto&gt;。然后 async 方法中的 return 将其包装回任务中 - 但由于它应该返回 Task&lt;IEnumerable&lt;DoctorDto&gt;&gt; 而不是 Task&lt;List&lt;DoctorDto&gt;&gt;,因此它将执行显式转换。您不能将Task&lt;List&lt;DoctorDto&gt;&gt; 转换为Task&lt;IEnumerable&lt;DoctorDto&gt;&gt;,但您可以List&lt;DoctorDto&gt; 转换为IEnumerable&lt;DoctorDto&gt;&gt; - 所以您只需要转换有效负载即可。 return await 为您处理。

标签: c# .net asynchronous


【解决方案1】:

Task&lt;T&gt; 根本不是协变类型。

虽然List&lt;T&gt;可以转换为IEnumerable&lt;T&gt;,但Task&lt;List&lt;T&gt;&gt;不能转换为Task&lt;IEnumerable&lt;T&gt;&gt;。在#4 中,Task.FromResult(doctors) 返回Task&lt;List&lt;DoctorDto&gt;&gt;

在 #3 中,我们有:

return await Task.FromResult(doctors)

等同于:

return await Task.FromResult<List<DoctorDto>>(doctors)

等同于:

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

这是因为List&lt;DoctorDto&gt; 可以转换为IEnumerable&lt;DoctorDto&gt;

【讨论】:

  • 但问题是,如果你添加 async/await 关键字,它突然可以转换。
  • 这显然解释了为什么第四个例子失败了,但问题实际上是关于为什么第三个例子通过。需要说明 async/await 生成的代码。
  • @avo 在这一点上实际上与协方差没有任何关系,List&lt;DoctorDto&gt; 只是实现了IEnumerable&lt;DoctorDto&gt;,因此可以这样提供。
  • @AdamHouldsworth,我同意,尽管我解释的主要思想和代码仍然正确。
  • @avo 确实,我只是想删除关于方差的讨论,在正确的地方使用时会令人困惑;-)
【解决方案2】:

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

但是,如果tTask&lt;T&gt;,那么await t的类型是T,如果T : UT可以转换为U

【讨论】:

    【解决方案3】:

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

    同样清楚的是,第三个示例正在做一些“额外”的事情,而第四个则没有。正如其他人所说,第 4 次失败是因为缺乏协方差(或者相反,我永远记不起他们走哪条路!),因为您直接尝试提供 Task&lt;List&lt;DoctorDto&gt;&gt; 的实例作为 @ 的实例987654324@.

    第三次通过的原因是await 添加了一大堆“支持代码”以使其按预期工作。此代码将Task&lt;T&gt; 解析为T,这样return await Task&lt;something&gt; 将返回在泛型Task&lt;T&gt; 中关闭的类型,在本例中为something

    然后方法签名返回Task&lt;T&gt; 并且它工作 再次由编译器解决,这需要Task&lt;T&gt;Taskvoid 用于异步方法,并简单地按摩你的T 回到 Task&lt;T&gt; 作为所有后台生成的 asyn/await 延续 gubbins 的一部分。

    正是这个从await 获取T 并需要将其返回 转换为Task&lt;T&gt; 的附加步骤为其提供了工作所需的空间。您不是试图采用现有的Task&lt;U&gt; 实例来满足Task&lt;T&gt;,而是创建一个全新的Task&lt;T&gt;,给它一个U : T,并且在构造时,隐式转换会如您所料那样发生(与您期望 IEnumerable&lt;T&gt; myVar = new List&lt;T&gt;(); 工作的方式完全相同)。

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

    【讨论】:

      猜你喜欢
      • 2018-02-05
      • 2010-11-30
      • 2018-06-10
      • 1970-01-01
      • 2015-03-12
      • 1970-01-01
      • 2012-07-27
      • 2011-06-09
      相关资源
      最近更新 更多