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;
}
}
}
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,并且用户从不进入最后一页,则您将永远不会计算列表中的最终项目。
如果 IEnumerable 表示一个无限集,则最好返回 yield 的另一种情况。考虑素数列表,或无限数量的随机数。您永远无法一次返回完整的 IEnumerable,因此您可以使用 yield-return 递增地返回列表。
在您的特定示例中,您拥有完整的产品列表,因此我将使用版本 2。
填充临时列表就像下载整个视频,而使用yield
就像流传输该视频。
void ConsumeLoop() {
foreach (Consumable item in ProduceList()) // might have to wait here
item.Consume();
}
IEnumerable<Consumable> ProduceList() {
while (KeepProducing())
yield return ProduceExpensiveConsumable(); // expensive
}
//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]
//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
public IEnumerable<IResult> HandleButtonClick() {
yield return Show.Busy();
var loginCall = new LoginResult(wsClient, Username, Password);
yield return loginCall;
this.IsLoggedIn = loginCall.Success;
yield return Show.NotBusy();
}
public LoginResult : IResult {
// Constructor to set private members...
public void Execute(ActionExecutionContext context) {
wsClient.LoginCompleted += (sender, e) => {
this.Success = e.Result;
Completed(this, new ResultCompletionEventArgs());
};
wsClient.Login(username, password);
}
public event EventHandler<ResultCompletionEventArgs> Completed = delegate { };
public bool Success { get; private set; }
}
这似乎是一个奇怪的建议,但是我通过阅读有关 Python 生成器的演示文稿来学习如何在 C#中使用yield
关键字:David M. Beazley 的http://www.dabeaz.com/generators/Generators.pdf 。您不需要了解太多 Python 就可以了解演示文稿 - 我不是。我发现它不仅有助于解释发电机的工作原理,而且对于您为什么要关心它很有帮助。
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
}
}
}
这两段代码实际上在做两件事。第一个版本将根据需要拉成员。第二个版本将在开始执行任何操作之前将所有结果加载到内存中。
这个答案没有对与错。哪种情况更好取决于具体情况。例如,如果您有一定的时间限制来完成查询,并且您需要对结果进行一些半复杂的操作,那么第二个版本可能会更好。但是要当心大型结果集,尤其是如果您以 32 位模式运行此代码时。执行此方法时,我多次被 OutOfMemory 异常咬伤。
但是要记住的关键是:差异在于效率。因此,您可能应该选择使您的代码更简单的任何一种,并仅在进行概要分析后才对其进行更改。
产量有两个重大用途
它有助于提供自定义迭代,而无需创建临时集合。 (加载所有数据并循环)
它有助于进行有状态的迭代。 (流式传输)
以下是我制作的简单视频,并进行了全面演示,以支持上述两点
克里斯 • 塞尔斯(Chris Sells) 用 C#编程语言讲述了这些陈述;
有时我会忘记 yield return 与 return 不同,因为 yield return 之后的代码可以执行。例如,第一次返回后的代码将永远无法执行:
int F() { return 1; return 2; // Can never be executed }
相反,可以在这里执行第一个收益率返回之后的代码:
IEnumerable<int> F() { yield return 1; yield return 2; // Can be executed }
这经常在 if 语句中给我带来痛苦:
IEnumerable<int> F() { if(...) { yield return 1; } // I mean this to be the only // thing returned yield return 2; // Oops! }
在这些情况下,记住收益率并不是像收益率那样的 “最终” 收益。
假设您的产品 LINQ 类使用类似的产量进行枚举 / 迭代,则第一个版本的效率更高,因为它每次迭代都仅产生一个值。
第二个示例是使用 ToList()方法将枚举器 / 迭代器转换为列表。这意味着它将手动遍历枚举器中的所有项目,然后返回一个平面列表。