基本上我想知道在代码中是否可以多次使用枚举。无论你是否提前破解,枚举总是会在下面的每个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;
}
}
}
答案 0 :(得分:14)
使用序列的代码很好。正如消费者指出的那样,如果树很深,产生枚举的代码可能会出现性能问题。
假设在最深处,你的树有四个深;想想四个节点上发生了什么。要获取该节点,请迭代调用迭代器的根,该迭代器调用迭代器,迭代器调用迭代器,该迭代器将节点传递回代码,该代码将节点传递回代码,将代码传递回...而不是仅仅将节点交给调用者,你已经制作了一个包含四个人的小斗队,并且他们在最终到达想要的循环之前,从一个对象到另一个对象的数据流动。
如果树只有四个深,可能没什么大不了的。但是假设树是一万个元素,并且有一千个节点在顶部形成链表,而剩下的九千个节点在底部。现在,当您迭代那九千个节点时,每个节点必须通过一千个迭代器,总共九百万个副本才能获取九千个节点。 (当然,你可能遇到了堆栈溢出错误并且也崩溃了。)
如果你有这个问题,解决这个问题的方法是自己管理堆栈,而不是在堆栈上推送新的迭代器。
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
答案 1 :(得分:13)
是的,这是合法的。 IEnumerable<T>
模式旨在支持单个源上的多个枚举。只能枚举一次的集合应该公开IEnumerator
。
答案 2 :(得分:6)
法律,是的。它是否会按预期运行取决于:
IEnumerable
返回的EffectsRecursive
的实现以及是否始终返回相同的集合;
是否要同时枚举同一组
如果它返回需要一些密集工作的IEnumerable,并且它不会在内部缓存结果,那么您可能需要自己.ToList()
。如果它确实缓存了结果,那么ToList()
会略微多余,但可能没什么坏处。
此外,如果GetEnumerator()
以典型/正确(*)的方式实施,那么您可以安全地枚举任意次数 - 每个foreach
都是新的致电GetEnumerator()
,该IEnumerator
会返回IEnumerator
的新实例。但它可能在某些情况下它返回已经部分或完全枚举的相同IEnumerable
实例,因此它实际上只取决于该特定{{1的特定用途}}
* 我很确定多次返回相同的枚举器实际上违反了该模式的隐含契约,但我已经看到了一些实现它的实现。
答案 3 :(得分:1)
最有可能,是的。 IEnumerable的大多数实现都返回一个新的IEnumerator,它从列表的开头开始。
答案 4 :(得分:0)
这完全取决于EffectsRecursive
的类型的实现。