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。
这有点离题了,但由于这个问题被标记为最佳实践,我将继续发表我的意见。对于这种类型的东西,我非常喜欢把它变成一个属性:
public static IEnumerable<Product> AllProducts
{
get {
using (AdventureWorksEntities db = new AdventureWorksEntities()) {
var products = from product in db.Product
select product;
return products;
}
}
}
当然,它更像一个样板,但使用它的代码将看起来更干净:
prices = Whatever.AllProducts.Select (product => product.price);
vs
prices = Whatever.GetAllProducts().Select (product => product.price);
注意:我不会对任何可能需要一段时间才能完成工作的方法这样做。
Yield return对于需要遍历数百万个对象的算法来说非常强大。考虑以下示例,您需要计算可能的拼车行程。首先我们生成可能的行程:
static IEnumerable<Trip> CreatePossibleTrips()
{
for (int i = 0; i < 1000000; i++)
{
yield return new Trip
{
Id = i.ToString(),
Driver = new Driver { Id = i.ToString() }
};
}
}
然后迭代每一次旅行:
static void Main(string[] args)
{
foreach (var trip in CreatePossibleTrips())
{
// possible trip is actually calculated only at this point, because of yield
if (IsTripGood(trip))
{
// match good trip
}
}
}
如果您使用List而不是yield,您将需要为内存分配100万个对象(~190mb),而这个简单的示例将花费~1400ms运行。但是,如果使用yield,就不需要将所有这些临时对象都放到内存中,而且算法速度会大大加快:本例只需要大约400ms就可以运行,完全不消耗内存。