可以多次重用IEnumerable集合吗?

Joa*_*nge 10 .net 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)
{
...
}
Run Code Online (Sandbox Code Playgroud)

编辑: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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Eri*_*ert 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

原始实现的时间复杂度为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())
Run Code Online (Sandbox Code Playgroud)

代替.

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

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

  • @Joan:你的第二个问题:你可以做的是在进入循环之前将"this"的孩子推到堆叠上以使球滚动. (2认同)

Jar*_*Par 13

是的,这是合法的.该IEnumerable<T>模式旨在支持单个源上的多个枚举.只能枚举一次的集合应该公开IEnumerator.


Rex*_*x M 6

合法,是的.它是否会按预期运行取决于:

  • IEnumerable返回的执行EffectsRecursive和是否总是返回相同的集合;

  • 是否要同时枚举相同的集合

如果它返回需要一些密集工作的IEnumerable,并且它不会在内部缓存结果,那么您可能需要.ToList()自己进行.如果它确实缓存了结果,那么ToList()会略微多余但可能没有坏处.

此外,如果GetEnumerator()典型/正确(*)的方式实现,那么您可以安全地枚举任意次数 - 每个都foreach将是一个新的调用GetEnumerator(),返回一个新的实例IEnumerator.但是在某些情况下它可能会返回IEnumerator已经部分或完全枚举的相同实例,因此它实际上只取决于该特定的特定用途IEnumerable.

*我很确定多次返回相同的枚举器实际上违反了模式的隐含合约,但我已经看到了一些实现它的实现.