如果队列上的IEnumerable迭代器应该使项目出列

Bro*_*ski 15 .net c# queue ienumerable iterator

我创建了一个自定义通用队列,它实现了一个通用的IQueue接口,该接口使用System.Collections.Generic命名空间中的通用Qu​​eue作为私有内部队列.示例已清除不相关的代码.

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

我希望保持与核心实现的一致性,并注意到核心Queue实现了IEnumerable,所以我将通过在类上显式实现IEnumerable或使用IQueue接口继承它来做同样的事情.

我想知道的是,当列举队列时,每个移动接下来要将下一个项目出列?我已经使用反射器来了解微软是如何做到的,他们所做的只是逐步通过队列私有阵列,但微软远非绝对不可靠,所以我想得到一般意见.

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();
    }

    ...
}
Run Code Online (Sandbox Code Playgroud)

我有两个想法,一方面我觉得迭代一个集合不应该改变集合状态,但另一方面,特别是我的特定实现,它会使用法看起来干净.

编辑

把事情放到上下文中.我正在实现的类在Dequeuing时执行Monitor.Wait并且队列中没有项目.当一个项目被放入队列时,有一个Monitor.Pulse.这允许一个线程将东西推送到队列而另一个线程基本上"监视"队列.

从编码的角度来看,我试图决定它看起来更干净:

foreach(QueueItem item in queue)
{
    DoSomethingWithThe(item);
}

//Or

while(systemIsRunning)
{
    DoSomethingWithThe(queue.Dequeue());
}
Run Code Online (Sandbox Code Playgroud)

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

编辑

有趣的是,我找到了一篇博客文章,其中有人做了这件事.

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

编辑

在我关闭之前最后一次刺伤.人们如何看待该类没有实现IEnumerable但是有一个IEnumerator GetEnumerator()方法使项目出列?.net语言支持duck typing,foreach是其中一种用途.也许这值得拥有它自己的问题?

编辑

提出了实现GetEnumerator方法而不在另一个问题中实现IEnumerable的问题.

Rob*_*sor 18

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

无法保证不会有两个并发迭代......


编辑以解决您的新评论:

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

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

如果您正在实现IEnumerable的子接口,并且不想支持IEnumerable,则应该抛出NotSupportedException.虽然这不会给你任何编译时警告,但运行时错误将非常清楚,而一个奇怪的IEnumerable实现可能会浪费你的未来几个小时.

  • @Keith一般来说这是一个很好的观点,但在OP的代码中,枚举器实现并没有表现出那个特定的问题. (3认同)

Eri*_*ert 12

绝对肯定的是,在迭代它时,你不应该改变一个集合.整迭代的是,他们提供了一个集合的只读非破坏性的视图.任何使用你的代码的人都会对它进行改变,这将是非常令人惊讶的.

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

  • 公平点,我没有考虑过. (2认同)

sup*_*cat 5

我建议您可能希望有一个名为DequeueAll的方法,该方法返回一个类的项,该类具有表示队列中所有内容的GetEnumerator方法,并清除队列(如果在iEnumerable周围添加了一个队列项)如果创建了新项,则新项应该出现在AllItemsDequeued中,而不是出现在队列中,或者出现在队列中,而不是当前调用中.如果此类实现了iEnumerable,则应该以这样的方式构造它,即使在创建和处理枚举器之后返回的对象仍然有效(允许多次枚举它).如果这样做是不切实际的,那么为类提供一个名称可能是有用的,该名称表明该类的对象不应该被持久化.人们仍然可以这样做

foreach(QueueItem theItem in theQueue.DequeueAll()) {}
但不太可能(错误地)将theQueue.DequeueAll的结果保存到iEnumerable.如果想要最大性能同时允许将Queue.DequeueAll的结果用作iEnumerable,则可以定义一个扩展的转换,它将获取DequeueAll结果的快照(从而允许丢弃旧项).