C# 可以多次重用IEnumerable集合吗?
基本上,我想知道是否可以在以后的代码中多次使用枚举。无论您是否提前中断,枚举是否会在下面的每个foreach情况下都重置,从而为我们提供从效果集合开始到结束的一致枚举C# 可以多次重用IEnumerable集合吗?,c#,.net,ienumerable,C#,.net,Ienumerable,基本上,我想知道是否可以在以后的代码中多次使用枚举。无论您是否提前中断,枚举是否会在下面的每个foreach情况下都重置,从而为我们提供从效果集合开始到结束的一致枚举 var effects = this.EffectsRecursive; foreach (Effect effect in effects) { ... } foreach (Effect effect in effects) { if(effect.Name = "Pixelate") break;
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;
}
}
}
公共IEnumerable效果
{
得到
{
for(int i=0;i
这一切都取决于生效类型的实现,是的。它是否能像您期望的那样工作取决于:
EffectsRecursive
返回的IEnumerable
的实现,以及它是否总是返回相同的集合
- 是否要同时枚举同一集合
如果它返回一个需要大量工作的IEnumerable,并且它没有在内部缓存结果,那么您可能需要.ToList()
自己完成它。如果它确实缓存了结果,那么ToList()
将略微冗余,但可能没有什么害处
另外,如果GetEnumerator()
是以典型/适当的(*)方式实现的,那么您可以安全地枚举任意次数-每个foreach
都将是对GetEnumerator()
的新调用,该调用返回IEnumerator
的新实例。但在某些情况下,它可能返回已部分或完全枚举的相同的IEnumerator
实例,因此这一切实际上取决于该特定IEnumerable
的特定预期用途
*我很确定多次返回同一个枚举数实际上违反了该模式的隐含约定,但我已经看到一些实现可以这样做。是的,这样做是合法的。IEnumerable
模式旨在支持单个源上的多个枚举。只能枚举一次的集合应该公开IEnumerator
。很可能是。IEnumerable的大多数实现返回一个新的IEnumerator,它从列表的开头开始。使用序列的代码很好。正如spender指出的,如果树很深,则生成枚举的代码可能会有性能问题
假设你的树最深处有四棵树;想想在四个深度的节点上发生了什么。为了得到那个节点,你迭代根,它调用一个迭代器,它调用一个迭代器,它调用一个迭代器,它将节点传递回代码,代码将节点传递回代码,代码将节点传递回。。。不只是将节点交给调用者,而是创建了一个包含四个人的小bucket旅,他们在数据最终到达需要它的循环之前,在对象之间来回移动数据
如果这棵树只有四英尺深,可能没什么大不了的。但是假设树是一万个元素,有一千个节点在顶部形成一个链表,其余的九千个节点在底部。现在,当你迭代这九千个节点时,每个节点都必须通过一千个迭代器,总共九百万个副本才能获取九千个节点。(当然,您可能会遇到堆栈溢出错误,并导致进程崩溃。)
如果您有这个问题,处理这个问题的方法是自己管理堆栈,而不是在堆栈上推新的迭代器
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);
}
}
相反
有关此问题的更多分析,请参阅我的同事韦斯·戴尔(Wes Dyer)关于此主题的文章:
顺便说一句,我添加了EffectsRecursive的实现。注意递归的产量。在创建状态机的过程中,有很多编译器的神奇之处。如果数据结构较大,则性能可能会比您预期的低/复杂。@spender:谢谢,有没有其他方法可供我使用?@Eric在下面的回答比我的回答更雄辩。@Eric:谢谢Eric,这是一个惊人的回答。我将像这样更改递归枚举。但在你的例子的最后,你的例子,它将从9000个项目中最顶端的父项开始,然后从上到下迭代9000个项目,然后从我们作为第一个元素迭代的第一个最顶端的父项开始,移动到其他最顶端的父项,直到我们以最左边的元素结束?这就是你说的“树从上到下,从右到左遍历”的意思吗?@Eric:Btw你说的“stack.Push(this);”也返回当前实例,对吗?在我的实现中,我只想返回当前效果的子效果,而不返回其本身。我能把它拿走吗?我觉得这会导致stack.Pop()行出现问题。你觉得怎么样?@Joan:这是你的第一个问题:假设树是A,左子B,右子C。这个算法按A,C,B的顺序生成。首先我们把A放在堆栈上。然后我们弹出A,把B和C放在堆栈上。然后我们弹出C,然后弹出B。B和C是从右到左,而不是从左到右。如果需要A、B、C,则在推送子列表之前反转它们。推A,弹出A,推C,推B,弹出B,弹出C,现在我们有了A,B,C。@Joan:这是你的第二个问题:你能做的是在进入循环之前把“这个”的子项推到堆栈上,让球滚动。@Joan:你忘了paren
foreach(var child in current.Effects.Reverse())