代码之家  ›  专栏  ›  技术社区  ›  Bronumski

队列上的IEnumerable迭代器是否应将项出列

  •  15
  • Bronumski  · 技术社区  · 14 年前

    我已经创建了一个自定义通用队列,它实现了一个通用I queue接口,该接口使用来自System.Collections.generic命名空间的通用队列作为私有内部队列。示例已清除不相关的代码。

    public interface IQueue<TQueueItem>
    {
        void Enqueue(TQueueItem queueItem);
        TQueueItem Dequeue();
    }
    
    public class CustomQueue<TQueueItem> : IQueue<TQueueItem>
    {
        private readonly Queue<TQueueItem> queue = new Queue<TQueueItem>();
        ...
        public void Enqueue(TQueueItem queueItem)
        {
            ...
            queue.Enqueue( queueItem );
            ...
        }
    
        public TQueueItem Dequeue()
        {
            ...
            return queue.Dequeue();
            ...
        }
    }
    

    我希望保持与核心实现的一致性,并注意到核心队列实现了IEnumerable,因此我将通过在类上显式实现IEnumerable或使用IQueu接口继承它来执行相同的操作。

    我想知道的是,当在队列上枚举时,每一个都应该将下一个项移出队列吗?我使用reflector查看了微软是如何做到的,他们所做的只是单步通过队列私有数组,但微软并不是绝对正确的,所以我想得到一个总体的意见。

    public class CustomQueue<TQueueItem> : IQueue<TQueueItem>, IEnumerable<TQueueItem>
    {
        ...
    
        public IEnumerator<TQueueItem> GetEnumerator()
        {
            while (queue.Count > 0)
            {
                yield return Dequeue();
            }
        }
    
        //Or
    
        public IEnumerator<TQueueItem> GetEnumerator()
        {
            return queue.GetEnumerator();
        }
    
        ...
    }
    

    我有两种想法,一方面我觉得遍历集合不应该改变集合的状态,但另一方面,特别是对于我的特定实现,它会使使用看起来很干净。

    编辑

    把事情放到上下文中去。我正在实现的类执行一个监视器。当出列时等待,队列中没有项目。当一个项目被放入队列时,有一个Monitor.Pulse。这允许一个线程将数据推送到队列中,而另一个线程基本上“监视”队列。

    从编码的角度来看,我试图确定哪个看起来更干净:

    foreach(QueueItem item in queue)
    {
        DoSomethingWithThe(item);
    }
    
    //Or
    
    while(systemIsRunning)
    {
        DoSomethingWithThe(queue.Dequeue());
    }
    

    对于我的特定实现,如果有多个进程出列项,这无关紧要。因为这是一个队列,所以他们都可以选择一个项,因为不应该多次处理任何项,因此使用队列。

    编辑

    有趣的是,我发现了一篇博客文章,其中有人已经做到了这一点。

    http://blogs.msdn.com/b/toub/archive/2006/04/12/blocking-queues.aspx

    编辑

    在我把它关上之前最后一刀。人们对这个类没有实现IEnumerable而是有一个IEnumerator GetEnumerator()方法来对项进行出列有什么感觉?net语言支持duck类型,foreach是其中一个用途。也许这值得我们问自己一个问题?

    编辑

    提出了在另一个方法中不实现IEnumerable的情况下实现GetEnumerator方法的问题 question

    10 回复  |  直到 7 年前
        1
  •  18
  •   Rob Fonseca-Ensor    14 年前

    迭代器应该始终是等幂的,也就是说,在对队列进行迭代时不要修改它。

    不能保证不会有两个并发迭代。。。


    编辑以处理您的新评论:

    当另一个程序员(比如你未来的self;)来为代码添加特性时,他们可能不会认为迭代器是一次性的。他们可能会添加一个日志语句,在使用它之前列出队列中的内容(oops)。

    我刚刚想到的另一件事是visual studio调试器经常会枚举您的类以供显示。这会导致一些非常混乱的错误:)

    如果要实现IEnumerable的子接口,并且不希望支持IEnumerable,则应抛出NotSupportedException。虽然这不会给您任何编译时警告,但运行时错误将非常明显,而奇怪的IEnumerable实现可能会浪费您今后的时间。

        2
  •  12
  •   Eric Lippert    14 年前

    绝对肯定的是,在迭代集合时不应该对其进行变异。整个 指向 迭代器的作用是提供集合的只读非破坏性视图。对于使用您的代码的任何人来说,查看它会改变它,这将是非常令人惊讶的。

    特别地: 您不希望在调试器中检查队列的状态来更改它 . 调试器像任何其他使用者一样调用IEnumerable,如果有副作用,则执行它们。

        3
  •  5
  •   supercat    14 年前

    我建议您可能需要一个名为DequeueAll的方法,该方法返回一个类的项,该类的GetEnumerator方法表示队列中的所有内容,并清除队列(如果在创建iEnumerable时添加了队列项,新项应该出现在present all to AllItemsDequeued中而不是队列中,或者出现在队列中而不是当前调用中)。如果这个类实现了iEnumerable,那么它的构造方式应该使返回的对象即使在创建并释放了枚举器之后仍然有效(允许多次枚举它)。如果这样做不切实际,那么给类指定一个名称可能会很有用,该名称建议不应持久化类的对象。一个人仍然可以

    foreach(QueueItem theItem in theQueue.DequeueAll()) {}
    但不太可能(错误地)将queue.DequeueAll的结果保存为iEnumerable。如果想要最大性能,同时允许队列的结果。DEQueLeALL都被用作IQueDebug,则可以定义一个加宽的CAST,它将对DeQueAlEALL结果进行快照(从而允许丢弃旧的项目)。
        4
  •  3
  •   Brian Gideon    14 年前

    我要抛开这股潮流说 . 这似乎是一个合理的方法。不过我得提个建议。也就是说,不要在 GetEnumerator 方法。取而代之 GetConsumingEnumerator 或者类似的东西。这样就很明显会发生什么 foreach 默认情况下,机制不会使用它。你不会是第一个这么做的人。事实上,微软已经通过 BlockingCollection 他们甚至用 GetConsumingEnumerator公司 作为方法 1个 . 我想你知道我接下来会建议什么吧? 2个

    1个 你觉得我是怎么想到这个名字的? 2个 为什么不直接用 封锁收集 ? 它能满足你的所有要求。

        5
  •  2
  •   BlueRaja - Danny Pflughoeft    14 年前

    我想说 ,按第二种方式操作。

    这不仅与内置队列类更加一致,而且与 IEnumerable<T> 接口是只读的。

    另外,你觉得这真的很直观吗?:

    //queue is full
    foreach(var item in queue)
        Console.WriteLine(item.ToString());
    //queue is empty!?
    
        6
  •  1
  •   Vlad    14 年前

    严格地说,队列只提供 push-back , pop-front , is-empty 也许 get-size 操作。您在队列旁边添加的所有内容都不是队列的一部分,因此,如果您决定提供额外的操作,则不需要遵守队列的语义。

    特别是,迭代器不是标准队列接口的一部分,因此您不需要让它们删除当前正在迭代的项。(正如其他人所指出的,这也与对迭代器的期望相矛盾。)

        7
  •  0
  •   Aliostad    14 年前

    在队列中循环应该 NOT 出列。

    这是为了检查队列的内容,而不是退出队列。这也是为什么 MSMQ MQ Series 作品。

        8
  •  0
  •   Klaus Byskov Pedersen    14 年前

    我觉得不应该。它将是非常隐式的,并且不会将此意图传达给任何习惯使用.net框架的人。

        9
  •  0
  •   Bryan    14 年前

    我要说第二个。枚举器绝对不应该更改集合的状态。

        10
  •  0
  •   Heiko Hatzfeld    14 年前

    我有一个代码,在这里property get不是等幂的。。。解码真是太痛苦了。请坚持手动出列。

    此外,您可能不是唯一一个处理队列的人,因此它可能会与多个消费者发生冲突。