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,因为你已经有了完整的列表。