C# 正确使用“收益回报”
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/410026/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Proper use of 'yield return'
提问by senfo
The yieldkeyword is one of those keywordsin C# that continues to mystify me, and I've never been confident that I'm using it correctly.
该产量关键字是其中的一个关键字,在C#是继续迷惑我,而且我正确使用它,我从来没有自信。
Of the following two pieces of code, which is the preferred and why?
以下两段代码中,哪个是首选,为什么?
Version 1:Using yield return
版本 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;
}
}
}
Version 2:Return the list
版本 2:返回列表
public static IEnumerable<Product> GetAllProducts()
{
using (AdventureWorksEntities db = new AdventureWorksEntities())
{
var products = from product in db.Product
select product;
return products.ToList<Product>();
}
}
采纳答案by abelenky
I tend to use yield-return when I calculate the next item in the list (or even the next group of items).
当我计算列表中的下一个项目(甚至下一组项目)时,我倾向于使用收益率-回报。
Using your Version 2, you must have the complete list before returning. By using yield-return, you really only need to have the next item before returning.
使用您的版本 2,您必须在返回之前获得完整列表。通过使用 yield-return,你真的只需要在返回之前拥有下一个项目。
Among other things, this helps spread the computational cost of complex calculations over a larger time-frame. For example, if the list is hooked up to a GUI and the user never goes to the last page, you never calculate the final items in the list.
除其他外,这有助于在更大的时间范围内分散复杂计算的计算成本。例如,如果列表连接到 GUI 并且用户永远不会转到最后一页,则您永远不会计算列表中的最终项目。
Another case where yield-return is preferable is if the IEnumerable represents an infinite set. Consider the list of Prime Numbers, or an infinite list of random numbers. You can never return the full IEnumerable at once, so you use yield-return to return the list incrementally.
收益率回报更可取的另一种情况是 IEnumerable 表示无限集。考虑素数列表,或无限随机数列表。您永远无法立即返回完整的 IEnumerable,因此您可以使用 yield-return 以增量方式返回列表。
In your particular example, you have the full list of products, so I'd use Version 2.
在您的特定示例中,您拥有完整的产品列表,因此我将使用版本 2。
回答by petr k.
And what about this?
而这个呢?
public static IEnumerable<Product> GetAllProducts()
{
using (AdventureWorksEntities db = new AdventureWorksEntities())
{
var products = from product in db.Product
select product;
return products.ToList();
}
}
I guess this is much cleaner. I do not have VS2008 at hand to check, though. In any case, if Products implements IEnumerable (as it seems to - it is used in a foreach statement), I'd return it directly.
我想这要干净得多。不过,我手头没有 VS2008 可以检查。在任何情况下,如果 Products 实现了 IEnumerable(就像它在 foreach 语句中使用的那样),我会直接返回它。
回答by Soviut
Assuming your products LINQ class uses a similar yield for enumerating/iterating, the first version is more efficient because its only yielding one value each time its iterated over.
假设您的产品 LINQ 类使用类似的产量进行枚举/迭代,第一个版本更有效,因为它每次迭代时只产生一个值。
The second example is converting the enumerator/iterator to a list with the ToList() method. This means it manually iterates over all the items in the enumerator and then returns a flat list.
第二个示例是使用 ToList() 方法将枚举器/迭代器转换为列表。这意味着它手动迭代枚举器中的所有项目,然后返回一个平面列表。
回答by recursive
Return the list directly. Benefits:
直接返回列表。好处:
- It's more clear
The list is reusable. (the iterator is not)not actually true, Thanks Jon
- 更清楚了
该列表可重复使用。(迭代器不是)实际上不是真的,谢谢乔恩
You should use the iterator (yield) from when you think you probably won't have to iterate all the way to the end of the list, or when it has no end. For example, the client calling is going to be searching for the first product that satisfies some predicate, you might consider using the iterator, although that's a contrived example, and there are probably better ways to accomplish it. Basically, if you know in advance that the whole list will need to be calculated, just do it up front. If you think that it won't, then consider using the iterator version.
当您认为您可能不必一直迭代到列表末尾时,或者当它没有末尾时,您应该使用迭代器 (yield)。例如,客户端调用将搜索满足某个谓词的第一个产品,您可以考虑使用迭代器,尽管这是一个人为的示例,并且可能有更好的方法来完成它。基本上,如果您事先知道需要计算整个列表,只需预先计算即可。如果您认为它不会,那么请考虑使用迭代器版本。
回答by Jason Baker
The two pieces of code are really doing two different things. The first version will pull members as you need them. The second version will load all the results into memory beforeyou start to do anything with it.
这两段代码实际上在做两件不同的事情。第一个版本将根据您的需要拉取成员。第二个版本会在您开始使用它之前将所有结果加载到内存中。
There's no right or wrong answer to this one. Which one is preferable just depends on the situation. For example, if there's a limit of time that you have to complete your query and you need to do something semi-complicated with the results, the second version could be preferable. But beware large resultsets, especially if you're running this code in 32-bit mode. I've been bitten by OutOfMemory exceptions several times when doing this method.
这个问题没有正确或错误的答案。哪个更可取取决于具体情况。例如,如果您必须完成查询的时间有限,并且您需要对结果做一些半复杂的事情,那么第二个版本可能更可取。但要注意大型结果集,尤其是当您在 32 位模式下运行此代码时。在执行此方法时,我多次被 OutOfMemory 异常咬住。
The key thing to keep in mind is this though: the differences are in efficiency. Thus, you should probably go with whichever one makes your code simpler and change it only after profiling.
但要记住的关键是:差异在于效率。因此,您可能应该采用任何一种使您的代码更简单的方法,并且仅在分析之后才对其进行更改。
回答by Mark A. Nicolosi
This is kinda besides the point, but since the question is tagged best-practices I'll go ahead and throw in my two cents. For this type of thing I greatly prefer to make it into a property:
这有点离题了,但由于问题被标记为最佳实践,我将继续投入我的两分钱。对于这种类型的东西,我非常喜欢把它变成一个属性:
public static IEnumerable<Product> AllProducts
{
get {
using (AdventureWorksEntities db = new AdventureWorksEntities()) {
var products = from product in db.Product
select product;
return products;
}
}
}
Sure, it's a little more boiler-plate, but the code that uses this will look much cleaner:
当然,它有点像样板,但使用它的代码看起来会更干净:
prices = Whatever.AllProducts.Select (product => product.price);
vs
对比
prices = Whatever.GetAllProducts().Select (product => product.price);
Note:I wouldn't do this for any methods that may take a while to do their work.
注意:对于任何可能需要一段时间才能完成工作的方法,我不会这样做。
回答by Robert Rossney
This is going to seem like a bizarre suggestion, but I learned how to use the yield
keyword in C# by reading a presentation on generators in Python: David M. Beazley's http://www.dabeaz.com/generators/Generators.pdf. You don't need to know much Python to understand the presentation - I didn't. I found it very helpful in explaining not just how generators work but why you should care.
这似乎是一个奇怪的建议,但我yield
通过阅读有关 Python 生成器的演示文稿,了解了如何在 C# 中使用关键字:David M. Beazley 的http://www.dabeaz.com/generators/Generators.pdf。您不需要了解太多 Python 就可以理解演示文稿 - 我没有。我发现它不仅有助于解释生成器的工作原理,还有助于解释为什么你应该关心它。
回答by Adam W. McKinley
I know this is an old question, but I'd like to offer one example of how the yield keyword can be creatively used. I have reallybenefited from this technique. Hopefully this will be of assistance to anyone else who stumbles upon this question.
我知道这是一个老问题,但我想提供一个示例,说明如何创造性地使用 yield 关键字。我真的从这项技术中受益。希望这对偶然发现这个问题的任何人都有帮助。
Note: Don't think about the yield keyword as merely being another way to build a collection. A big part of the power of yield comes in the fact that execution is pausedin your method or property until the calling code iterates over the next value. Here's my example:
注意:不要认为 yield 关键字仅仅是构建集合的另一种方式。yield 的很大一部分力量来自这样一个事实,即执行在您的方法或属性中暂停,直到调用代码迭代下一个值。这是我的例子:
Using the yield keyword (alongside Rob Eisenburg's Caliburn.Micro coroutinesimplementation) allows me to express an asynchronous call to a web service like this:
使用 yield 关键字(以及 Rob Eisenburg 的Caliburn.Micro 协程实现)允许我表达对 Web 服务的异步调用,如下所示:
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();
}
What this will do is turn my BusyIndicator on, call the Login method on my web service, set my IsLoggedIn flag to the return value, and then turn the BusyIndicator back off.
这将打开 BusyIndicator,调用 Web 服务上的 Login 方法,将 IsLoggedIn 标志设置为返回值,然后关闭 BusyIndicator。
Here's how this works: IResult has an Execute method and a Completed event. Caliburn.Micro grabs the IEnumerator from the call to HandleButtonClick() and passes it into a Coroutine.BeginExecute method. The BeginExecute method starts iterating through the IResults. When the first IResult is returned, execution is paused inside HandleButtonClick(), and BeginExecute() attaches an event handler to the Completed event and calls Execute(). IResult.Execute() can perform either a synchronous or an asynchronous task and fires the Completed event when it's done.
这是它的工作原理: IResult 有一个 Execute 方法和一个 Completed 事件。Caliburn.Micro 从对 HandleButtonClick() 的调用中获取 IEnumerator 并将其传递给 Coroutine.BeginExecute 方法。BeginExecute 方法开始遍历 IResults。当第一个 IResult 返回时,执行在 HandleButtonClick() 中暂停,而 BeginExecute() 将事件处理程序附加到 Completed 事件并调用 Execute()。IResult.Execute() 可以执行同步或异步任务,并在完成时触发 Completed 事件。
LoginResult looks something like this:
LoginResult 看起来像这样:
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; }
}
It may help to set up something like this and step through the execution to watch what's going on.
设置这样的东西并逐步执行以观察正在发生的事情可能会有所帮助。
Hope this helps someone out! I've really enjoyed exploring the different ways yield can be used.
希望这可以帮助别人!我非常喜欢探索使用 yield 的不同方式。
回答by IntelligentBinary
I would have used version 2 of the code in this case. Since you have the full-list of products available and that's what expected by the "consumer" of this method call, it would be required to send the complete information back to the caller.
在这种情况下,我会使用代码的第 2 版。由于您拥有可用产品的完整列表,并且这是此方法调用的“消费者”所期望的,因此需要将完整信息发送回调用方。
If caller of this method requires "one" information at a time and the consumption of the next information is on-demand basis, then it would be beneficial to use yield return which will make sure the command of execution will be returned to the caller when a unit of information is available.
如果此方法的调用者一次需要“一个”信息,并且下一个信息的消耗是按需的,那么使用 yield return 将是有益的,这将确保执行命令将在何时返回给调用者一个信息单元是可用的。
Some examples where one could use yield return is:
一些可以使用收益回报的例子是:
- Complex, step-by-step calculation where caller is waiting for data of a step at a time
- Paging in GUI - where user might never reach to the last page and only sub-set of information is required to be disclosed on current page
- 复杂的逐步计算,调用者一次等待一个步骤的数据
- GUI 中的分页 - 用户可能永远不会到达最后一页,并且只需要在当前页面上披露信息的子集
To answer your questions, I would have used the version 2.
为了回答您的问题,我会使用版本 2。
回答by Kache
As a conceptual example for understanding when you ought to use yield
, let's say the method ConsumeLoop()
processes the items returned/yielded by ProduceList()
:
作为理解何时应该使用的概念示例,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
}
Without yield
, the call to ProduceList()
might take a long time because you have to complete the list before returning:
如果没有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]
Using yield
, it becomes rearranged, sort of working "in parallel":
使用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
And lastly, as many before have already suggested, you should use Version 2 because you already have the completed list anyway.
最后,正如之前许多人已经建议的那样,您应该使用第 2 版,因为无论如何您已经有了完整的列表。