【问题标题】:Stack and Queue enumeration order堆栈和队列枚举顺序
【发布时间】:2016-06-09 12:02:17
【问题描述】:

我知道List 枚举器保证枚举顺序并尊重最后的排序操作,我知道DictionaryHashSet 不能,即你可以确定 p>

Dictionary<string, string> dictionary = ...;

foreach(var pair in dictionary)
{

}

将按照它们附加的顺序处理对。

StackQueue 呢?他们的枚举器是否保证任何顺序?

【问题讨论】:

  • 你在比较不同的东西。堆栈、队列、数组、列表具有强大的排序保证,而字典则没有。您遇到过具体问题吗?
  • @PanagiotisKanavos 不,我没有。但这无关紧要。说实话,你这几年的年轻人,我使用了Dictionary 枚举,并且代码被构建为依赖于枚举顺序,这很糟糕。我从未偶然发现Dictionary 更改枚举顺序的情况。这不会使错误更加糟糕。
  • @MatthewWatson 感谢您的提示。它很近,但还不够近。我需要一份有据可查的订单证据以确保枚举。仅仅观察是不好的。
  • RE: 字典:字典可能改变顺序的唯一时间是Dictionary 被调整大小。枚举未定义的原因是因为它处于内部散列顺序(GetHashCode() 进一步散列到有限数量的桶中)。

标签: c# .net


【解决方案1】:

对于Stack,枚举当前由一个名为StackEnumerator 的嵌套私有类完成(来自Reference Source):

private class StackEnumerator : IEnumerator, ICloneable
{
    private Stack _stack;
    private int _index;
    private int _version;
    private Object currentElement;

    internal StackEnumerator(Stack stack) {
        _stack = stack;
        _version = _stack._version;
        _index = -2;
        currentElement = null;
    }

    public Object Clone()
    {
        return MemberwiseClone();
    }

    public virtual bool MoveNext() {
        bool retval;
        if (_version != _stack._version) throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumFailedVersion));
        if (_index == -2) {  // First call to enumerator.
            _index = _stack._size-1;
            retval = ( _index >= 0);
            if (retval)
                currentElement = _stack._array[_index];
            return retval;
        }
        if (_index == -1) {  // End of enumeration.
            return false;
        }

        retval = (--_index >= 0);
        if (retval)
            currentElement = _stack._array[_index];
        else
            currentElement = null;
        return retval;
    }

    public virtual Object Current {
        get {
            if (_index == -2) throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumNotStarted));
            if (_index == -1) throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumEnded));
            return currentElement;
        }
    }

    public virtual void Reset() {
        if (_version != _stack._version) throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumFailedVersion));
        _index = -2;
        currentElement = null;
    }
}    

注意它是如何从设置为_stack._size-1 的索引开始枚举并递减索引以按后进先出顺序返回每个元素的。

但是,由于没有记录,因此您不能保证它会一直这样(尽管微软现在改变枚举器的工作方式将是疯狂的!) p>

您可以检查嵌套的QueueEnumerator 类的实现,并类似地确定枚举是按照项目出列的顺序完成的。

Stack.GetEnumerator() 强烈暗示使用 LIFO 顺序。

如果您查看 the example for Stack&lt;T&gt;.GetEnumerator() in Microsoft's documentation 并检查声明的输出,您可以看到它是 LIFO 顺序的。

这强烈表明 Microsoft 完全打算按 LIFO 顺序枚举堆栈 - 但他们忘记(或没有费心)明确记录这一点!

【讨论】:

  • 我明白你的意思。 Strongly implies 至少是一些东西。在接受这个答案之前,我会先寻找一些额外的答案。最佳atm。
【解决方案2】:

Queue 是一个先进先出 (FIFO) 集合(在文档中这么说)。这意味着枚举器按添加顺序为您提供项目。

Stack 是一个后进先出 (LIFO) 集合。这意味着枚举器以与添加方式相反的顺序为您提供项目。

堆栈和队列是非常标准的计算机科学构造,因此如果没有严重的反弹,它们真的无法重新使用。当您查看 GetEnumerator() 函数的示例时,它清楚地记录了枚举的顺序:

Stack Enumeration:

    Stack<string> numbers = new Stack<string>();
    numbers.Push("one");
    numbers.Push("two");
    numbers.Push("three");
    numbers.Push("four");
    numbers.Push("five");

    // A stack can be enumerated without disturbing its contents.
    foreach( string number in numbers )
    {
        Console.WriteLine(number);
    }

    /* This code example produces the following output:

     five
     four
     three
     two
     one

    */

Queue Enumeration:

    Queue<string> numbers = new Queue<string>();
    numbers.Enqueue("one");
    numbers.Enqueue("two");
    numbers.Enqueue("three");
    numbers.Enqueue("four");
    numbers.Enqueue("five");

    // A queue can be enumerated without disturbing its contents.
    foreach( string number in numbers )
    {
        Console.WriteLine(number);
    }

    /* This code example produces the following output:

     one
     two
     three
     four
     five

    */

同样,对于基本的计算机科学定义,枚举器或迭代器必须以集合的自然顺序呈现元素。特定的集合类型具有定义的顺序。

警告

请注意,虽然枚举过程确实反映了 FIFO 和 LIFO 集合 (ref) 的自然顺序,但这不是队列 (ref) 和堆栈 (ref) 的意图 /em> 被使用。它们旨在与Enqueue()/Dequeue()Push()/Pop()/Peek() 交互一起使用。 Microsoft 包含枚举器以使所有内容与基本ICollection&lt;T&gt; 接口保持一致,并使枚举器保持集合的自然顺序。

队列的目的是提供可以按顺序处理的工作管道。 Stack 的目的是提供一种在本地工作完成时返回到先前上下文的方法。它们旨在一次处理一个项目。使用枚举器类型的侧步骤迭代集合,整个目的并且不会从队列/堆栈中删除项目。它本质上是对那里所有项目的一瞥。

【讨论】:

  • 我认为这里的问题与未明确记录的枚举数的行为有关。当然,.Push().Pop().Enqueue().Dequeue() 具有明确定义的行为 - 但它们不是枚举器。
  • @MatthewWatson 你明白我的意思。我需要的不仅仅是逻辑含义。
  • 查看 Stack GetEnumerator() 的记录行为:msdn.microsoft.com/en-us/library/yfw8w9at(v=vs.110).aspx 它记录了枚举按照集合的规定顺序进行。如果枚举器返回元素的顺序对集合来说是不正常的,这将是一个错误。
  • @BerinLoritsch 这就是 MatthewWatson 在他的回答中提出的。
  • @BerinLoritsch 为什么Peek 对于这些东西的使用方式是可以的,而枚举不是?有什么大的区别?是的,我很高兴有一个带有索引的Peek,为什么不呢?
【解决方案3】:

是的。它似乎没有明确记录,但元素的枚举顺序与您将它们弹出/出列时的顺序相同。

【讨论】:

  • 有这方面的书面证据吗?
  • @ZverevEugene,刚查了MSDN,好像没有提到。
  • @MatthewWatson 确实;它只是没有明确说明它会影响枚举顺序。
  • 参考来源是实现细节,而不是文档。
  • @LasseV.Karlsen 我同意这不是文档 - 知道微软是否打算永远不改变行为的唯一方法是让他们记录它。但是,他们现在改变其工作方式的可能性几乎为零。
猜你喜欢
  • 2013-09-18
  • 2021-02-22
  • 2015-06-12
  • 2014-07-21
  • 2015-11-16
  • 2019-03-07
  • 2021-04-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多