Law*_*ley 158 .net c# yield yield-return
这个问题在这里已经有了答案:
在返回IEnumerable时是否有理由不使用'yield return'?
关于这些的好处,这里有几个有用的问题yield return.例如,
我正在寻找关于何时不使用的想法yield return.例如,如果我预计需要返回一个集合中的所有项目,它没有看起来就像yield是有用的,对不对?
什么情况下使用yield会限制,不必要,让我陷入困境,或者应该避免?
Eri*_*ert 144
什么情况下,收益率的使用会受到限制,不必要,让我陷入困境,或者应该避免?
在处理递归定义的结构时,仔细考虑使用"yield return"是个好主意.例如,我经常看到这个:
public static IEnumerable<T> PreorderTraversal<T>(Tree<T> root)
{
if (root == null) yield break;
yield return root.Value;
foreach(T item in PreorderTraversal(root.Left))
yield return item;
foreach(T item in PreorderTraversal(root.Right))
yield return item;
}
Run Code Online (Sandbox Code Playgroud)
完全合理的代码,但它有性能问题.假设树很深.然后最多会有O(h)嵌套迭代器构建.在外部迭代器上调用"MoveNext"然后将对MoveNext进行O(h)嵌套调用.因为对于具有n个项的树,它执行O(n)次,这使得算法O(hn).并且由于二叉树的高度为lg n <= h <= n,这意味着该算法最多为O(n lg n),最差时为O(n ^ 2),最佳情况为O(lg) n)并且在堆栈空间中更糟糕的情况是O(n).它是堆空间中的O(h),因为每个枚举器都在堆上分配.(关于C#的实现我知道;一致的实现可能有其他堆栈或堆空间特性.)
但迭代树可以是时间上的O(n)和堆栈空间中的O(1).你可以这样写:
public static IEnumerable<T> PreorderTraversal<T>(Tree<T> root)
{
var stack = new Stack<Tree<T>>();
stack.Push(root);
while (stack.Count != 0)
{
var current = stack.Pop();
if (current == null) continue;
yield return current.Value;
stack.Push(current.Left);
stack.Push(current.Right);
}
}
Run Code Online (Sandbox Code Playgroud)
它仍然使用收益率回报,但它更聪明.现在我们在时间上是O(n),在堆空间中是O(h),在堆栈空间中是O(1).
进一步阅读:请参阅Wes Dyer关于此主题的文章:
http://blogs.msdn.com/b/wesdyer/archive/2007/03/23/all-about-iterators.aspx
Pop*_*lin 56
什么情况下,收益率的使用会受到限制,不必要,让我陷入困境,或者应该避免?
我可以想到几个案例,IE:
返回现有迭代器时,请避免使用yield return.例:
// Don't do this, it creates overhead for no reason
// (a new state machine needs to be generated)
public IEnumerable<string> GetKeys()
{
foreach(string key in _someDictionary.Keys)
yield return key;
}
// DO this
public IEnumerable<string> GetKeys()
{
return _someDictionary.Keys;
}
Run Code Online (Sandbox Code Playgroud)当您不希望延迟该方法的执行代码时,请避免使用yield return.例:
// Don't do this, the exception won't get thrown until the iterator is
// iterated, which can be very far away from this method invocation
public IEnumerable<string> Foo(Bar baz)
{
if (baz == null)
throw new ArgumentNullException();
yield ...
}
// DO this
public IEnumerable<string> Foo(Bar baz)
{
if (baz == null)
throw new ArgumentNullException();
return new BazIterator(baz);
}
Run Code Online (Sandbox Code Playgroud)Ahm*_*eed 32
要意识到的关键是什么yield是有用的,然后你可以决定哪些情况不会从中受益.
换句话说,当您不需要延迟评估序列时,您可以跳过使用yield.那会是什么时候?当你不介意立即把你的整个收藏品留在记忆中时.否则,如果你有一个巨大的序列会对内存产生负面影响,你会想要yield一步一步地使用它(即懒洋洋地).在比较两种方法时,分析器可能会派上用场.
注意大多数LINQ语句如何返回IEnumerable<T>.这允许我们不断地将不同的LINQ操作串在一起,而不会在每个步骤(即延迟执行)中对性能产生负面影响.另一张图片是ToList()在每个LINQ语句之间调用.这将导致在执行下一个(链接的)LINQ语句之前立即执行每个前面的LINQ语句,从而放弃延迟评估的任何好处并利用IEnumerable<T>所需的直到.
Str*_*ior 23
这里有很多优秀的答案.我想补充一点:不要在已经知道值的小集合或空集合中使用yield return:
IEnumerable<UserRight> GetSuperUserRights() {
if(SuperUsersAllowed) {
yield return UserRight.Add;
yield return UserRight.Edit;
yield return UserRight.Remove;
}
}
Run Code Online (Sandbox Code Playgroud)
在这些情况下,创建Enumerator对象比生成数据结构更昂贵,更冗长.
IEnumerable<UserRight> GetSuperUserRights() {
return SuperUsersAllowed
? new[] {UserRight.Add, UserRight.Edit, UserRight.Remove}
: Enumerable.Empty<UserRight>();
}
Run Code Online (Sandbox Code Playgroud)
这是我的基准测试的结果:
这些结果显示执行操作1,000,000次所花费的时间(以毫秒为单位).数字越小越好.
在重新审视时,性能差异并不足以让人担心,因此您应该选择最容易阅读和维护的内容.
我很确定上述结果是在禁用编译器优化的情况下实现的.使用现代编译器在发布模式下运行时,两者之间的性能几乎无法区分.选择最易读的内容.
Qwe*_*tie 18
埃里克·利珀特(Eric Lippert)提出了一个很好的观点(太糟糕了,C#没有像Cw那样的流扁平化).我想补充一点,有时枚举过程由于其他原因而很昂贵,因此如果您打算多次迭代IEnumerable,则应该使用列表.
例如,LINQ-to-objects建立在"yield return"之上.如果您编写了一个慢LINQ查询(例如,将大型列表过滤到一个小列表中,或者进行排序和分组),那么调用ToList()查询结果可能是明智的,以避免多次枚举(实际上多次执行查询).
如果您在"yield return"和List<T>编写方法之间进行选择,请考虑:计算每个元素是否都很昂贵,调用者是否需要多次枚举结果?如果您知道答案是肯定的,那么您就不应该使用yield return(例如,除非产生的列表非常大,否则您将无法承受它将使用的内存.请记住,另一个好处yield是结果列表没有不必一次全部留在记忆中.
不使用"收益率返回"的另一个原因是交错操作是危险的.例如,如果您的方法看起来像这样,
IEnumerable<T> GetMyStuff() {
foreach (var x in MyCollection)
if (...)
yield return (...);
}
Run Code Online (Sandbox Code Playgroud)
如果MyCollection有可能因调用者所做的事情而改变,那么这很危险:
foreach(T x in GetMyStuff()) {
if (...)
MyCollection.Add(...);
// Oops, now GetMyStuff() will throw an exception
// because MyCollection was modified.
}
Run Code Online (Sandbox Code Playgroud)
yield return 每当调用者改变屈服函数假定不改变的东西时,都会引起麻烦.
yield return如果方法具有您期望调用方法的副作用,我会避免使用.这是由于Pop Catalin提到的延迟执行.
一个副作用可能是修改系统,这可能发生在像这样的方法中IEnumerable<Foo> SetAllFoosToCompleteAndGetAllFoos(),这违反了单一责任原则.这很明显(现在......),但不太明显的副作用可能是设置缓存结果或类似优化.
我的经验法则(再次,现在......)是:
yield在返回的对象需要一些处理时使用yieldyield并确保扩展迭代的好处超过成本当您需要随机访问时,收益率将是限制/不必要的.如果你需要访问元素0然后元素99,你几乎已经消除了延迟评估的有用性.
| 归档时间: |
|
| 查看次数: |
41546 次 |
| 最近记录: |