协慌网

登录 贡献 社区

正确使用 '收益率'

yield关键字是 C#中那些继续使我神秘化的关键字之一,而且我从未确信我正确使用它。

以下两段代码中,哪个是首选,为什么?

版本 1:使用收益率返回

public static IEnumerable<Product> GetAllProducts()
{
    using (AdventureWorksEntities db = new AdventureWorksEntities())
    {
        var products = from product in db.Product
                       select product;

        foreach (Product product in products)
        {
            yield return product;
        }
    }
}

版本 2:返回列表

public static IEnumerable<Product> GetAllProducts()
{
    using (AdventureWorksEntities db = new AdventureWorksEntities())
    {
        var products = from product in db.Product
                       select product;

        return products.ToList<Product>();
    }
}

答案

当我计算列表中的下一个项目(甚至是下一组项目)时,我倾向于使用 yield-return。

使用版本 2,您必须在返回之前拥有完整列表。通过使用 yield-return,您实际上只需要在返回之前拥有下一个项目。

除此之外,这有助于在更大的时间范围内分散复杂计算的计算成本。例如,如果列表连接到 GUI 并且用户永远不会转到最后一页,则永远不会计算列表中的最终项目。

另一种情况,其中 yield-return 是优选的,如果 IEnumerable 表示无限集。考虑素数列表,或无限的随机数列表。您永远不能一次返回完整的 IEnumerable,因此您使用 yield-return 以递增方式返回列表。

在您的特定示例中,您有完整的产品列表,因此我将使用版本 2。

填充临时列表就像下载整个视频一样,而使用yield就像流式传输视频一样。

作为理解何时应该使用yield的概念性示例,假设方法ConsumeLoop()处理由ProduceList()返回 / 生成的项:

void ConsumeLoop() {
    foreach (Consumable item in ProduceList())        // might have to wait here
        item.Consume();
}

IEnumerable<Consumable> ProduceList() {
    while (KeepProducing())
        yield return ProduceExpensiveConsumable();    // expensive
}

如果没有yield ,对ProduceList()的调用可能需要很长时间,因为您必须在返回之前完成列表:

//pseudo-assembly
Produce consumable[0]                   // expensive operation, e.g. disk I/O
Produce consumable[1]                   // waiting...
Produce consumable[2]                   // waiting...
Produce consumable[3]                   // completed the consumable list
Consume consumable[0]                   // start consuming
Consume consumable[1]
Consume consumable[2]
Consume consumable[3]

使用yield ,它会重新排列,有点 “并行” 工作:

//pseudo-assembly
Produce consumable[0]
Consume consumable[0]                   // immediately Consume
Produce consumable[1]
Consume consumable[1]                   // consume next
Produce consumable[2]
Consume consumable[2]                   // consume next
Produce consumable[3]
Consume consumable[3]                   // consume next

最后,正如许多人之前已经建议的那样,你应该使用版本 2,因为你已经有了完整的列表。