在While循环中使用Yield(PriorityQueue实现)

时间:2014-01-07 21:33:55

标签: c# .net yield-return

我最近一直在玩各种类型的PriorityQueue类,我遇到过一些我不太了解的行为。

这里是我正在运行的单元测试的片段:

        PriorityQueue<Int32> priorityQueue = new PriorityQueue<Int32>();
        Randomizer r = new Randomizer();
        priorityQueue.AddRange(r.GetInts(Int32.MinValue, Int32.MaxValue, r.Next(300, 10000)));

        priorityQueue.PopFront(); // Gets called, and works correctly

        Int32 numberToPop = priorityQueue.Count / 3;
        priorityQueue.PopFront(numberToPop); // Does not get called, an empty IEnumberable<T> (T is an Int32 here) is returned

正如我在评论中所指出的,PopFront()被调用并正常运行,但是当我尝试调用PopFront(numberToPop)时,该方法根本没有被调用,因为它甚至没有进入方法

以下是方法:

    public T PopFront()
    {
        if (items.Count == 0)
        {
            throw new InvalidOperationException("No elements exist in the queue");
        }

        T item = items[0];
        items.RemoveAt(0);
        return item;
    }

    public IEnumerable<T> PopFront(Int32 numberToPop)
    {
        Debug.WriteLine("PriorityQueue<T>.PopFront({0})", numberToPop);
        if (numberToPop > items.Count)
        {
            throw new ArgumentException(@"The numberToPop exceeds the number 
                                          of elements in the queue", "numberToPop");
        }

        while (numberToPop-- > 0)
        {
            yield return PopFront();
        }
    }

现在,我之前已经实现了重载的PopFront函数,如下所示:

    public IEnumerable<T> PopFront(Int32 numberToPop)
    {
        Console.WriteLine("PriorityQueue<T>.PopFront({0})", numberToPop);
        if (numberToPop > items.Count)
        {
            throw new ArgumentException(@"The numberToPop exceeds the number 
                                          of elements in the queue", "numberToPop");
        }

        var poppedItems = items.Take(numberToPop);
        Clear(0, numberToPop);
        return poppedItems;
    }

之前的实施(上文)按预期工作。尽管如此,我显然知道我对yield语句的使用是不正确的(很可能因为我正在删除然后返回PopFront()函数中的元素),但我真正感兴趣的是为什么从不调用PopFront(Int32 numberToPop),如果没有调用它,为什么它会返回一个空的IEnumerable?

非常感谢任何帮助/解释为什么会发生这种情况。

1 个答案:

答案 0 :(得分:5)

使用yield return时,编译器会为您创建状态机。在您开始枚举(foreachToList)您的方法返回的IEnumerable<T>之前,您的代码才会开始执行。

来自yield documentation

  

在foreach循环的迭代中,为元素调用MoveNext方法。此调用执行MyIteratorMethod的主体,直到达到下一个yield return语句。 yield return语句返回的表达式不仅决定了循环体消耗的元素变量的值,还决定了元素的Current属性,即IEnumerable。

     

在foreach循环的每个后续迭代中,迭代器主体的执行从它停止的地方继续,当它到达yield return语句时再次停止。当迭代器方法结束或yield break语句到达时,foreach循环完成。