可以多次重用IEnumerable集合吗?

时间:2011-02-08 00:24:36

标签: c# .net 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;
        }
    }
}

5 个答案:

答案 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的类型的实现。