C# · 12月 30, 2021

c# – 可以多次重复使用IEnumerable集合吗?

基本上我想知道如果可以在代码中多次使用枚举.无论你是否早点休息,枚举总是会在下面的每个foreach案例中重置,所以给我们从效果集合的开始到结束一致的列举? var effects = this.EffectsRecursive;foreach (Effect effect in effects){…}foreach (Effect effect in effects){ if(effect.Name = “Pixelate”) break;}foreach (Effect effect in effects){…}

编辑:EffectsRecursive的实现是这样的:

public IEnumerable<Effect> Effects{ get { for ( int i = 0; i < this.IEffect.NumberOfChildren; ++i ) { IEffect effect = this.IEffect.GetChildEffect ( i ); if ( effect != null ) yield return new Effect ( effect ); } }}public IEnumerable<Effect> EffectsRecursive{ get { foreach ( Effect effect in this.Effects ) { yield return effect; foreach ( Effect seffect in effect.ChildrenRecursive ) yield return seffect; } }}解决方法 消耗序列的代码很好.正如花费者指出的那样,如果树很深,产生枚举的代码可能会有性能问题.

假设在最深处,你的树是四深;想想在四深的节点上会发生什么.要获取该节点,您将遍历根,调用一个迭代器,该迭代器调用一个迭代器,该迭代器将节点传递回传递节点的代码返回到传递节点的代码.将节点传递给调用者,您已经在其中配置了一个小桶旅,其中有四个人,他们正在将数据从对象转到对象,然后才能到达需要的循环.

如果树只有四深,大概不大.但是假设树是一万个元素,并且有一千个节点在顶部形成一个链表,底部剩下的九千个节点.现在,当您重复这九千个节点时,每个节点必须通过一千个迭代器,总共有900万个拷贝来获取9000个节点. (当然,你可能已经收到了一个堆栈溢出错误,并且崩溃了这个进程.)

处理这个问题的方法是自己管理堆栈,而不是在栈上推新的迭代器.

public IEnumerable<Effect> EffectsNotRecursive() { var stack = new Stack<Effect>(); stack.Push(this); while(stack.Count != 0) { var current = stack.Pop(); yield return current; foreach(var child in current.Effects) stack.Push(child); }}

原始实现具有O(nd)的时间复杂度,其中n是节点数,d是树的平均深度;因为在最坏的情况下d可以是O(n),并且在最好的情况下是O(lg n),这意味着算法在时间上在O(n lg n)和O(n ^ 2)之间.在堆空间(对于所有迭代器)和O(d)在堆栈空间中(对于所有递归调用)为O(d).

新实现具有O(n)的时间复杂度,在堆空间中为O(d),在堆栈空间中为O(1).

这一点的一个不足之处在于订单是不同的;在新算法中,树从上到下,从右到左遍历,而不是从上到下,从左到右.如果这样麻烦你就可以说

foreach(var child in current.Effects.Reverse())

代替.

有关这个问题的更多分析,请参阅我的同事Wes Dyer关于这个问题的文章:

http://blogs.msdn.com/b/wesdyer/archive/2007/03/23/all-about-iterators.aspx