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

时间:2010-11-15 22:41:02

标签: c# .net iterator queue ienumerable

我创建了一个自定义通用队列,它实现了一个通用的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();
        ...
    }
}

我希望保持与核心实现的一致性,并注意到核心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();
    }

    ...
}

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

修改

把事情放到上下文中。我正在实现的类在Dequeuing时执行Monitor.Wait并且队列中没有项目。当一个项目被放入队列时,有一个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 typing,foreach是其中一种用途。也许这值得拥有它自己的问题?

修改

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

10 个答案:

答案 0 :(得分:18)

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

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


编辑以解决您的新评论:

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

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

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

答案 1 :(得分:12)

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

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

答案 2 :(得分:5)

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

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

< / p>

答案 3 :(得分:3)

我要离开这个潮流并说。这似乎是一种合理的方法。虽然我必须提出一个建议。也就是说,不要在GetEnumerator方法中实现这个消耗迭代器。而是将其称为GetConsumingEnumerator或类似的东西。这样很明显会发生什么,foreach机制默认不会使用它。你不会是第一个这样做的人。实际上,Microsoft已经通过BlockingCollection类在BCL中执行此操作,他们甚至使用GetConsumingEnumerator作为方法 1 。我想你知道我接下来会建议什么吗? 2

1 您认为我如何提出姓名建议? 2 为什么不使用BlockingCollection?它可以满足您的所有要求。

答案 4 :(得分:2)

我会说,第二种方式。

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

另外,这对您来说真的很直观吗?:

//queue is full
foreach(var item in queue)
    Console.WriteLine(item.ToString());
//queue is empty!?

答案 5 :(得分:1)

严格来说,队列仅提供push-backpop-frontis-emptyget-size次操作。您添加的所有内容都不是队列的一部分,因此如果您决定提供其他操作,则无需保留队列的语义。

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

答案 6 :(得分:0)

通过队列迭代NOT出列。

这旨在查看检查队列的内容不要出列。这也是MSMQMQ Series的工作原理。

答案 7 :(得分:0)

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

答案 8 :(得分:0)

我会说第二个。你的枚举器肯定不应该改变集合的状态。

答案 9 :(得分:0)

我有一个代码,其中一个属性得到的不是幂等的...解码是一件很痛苦的事。请坚持手动Dequeue。

此外,您可能不是唯一一个在队列中工作的人,因此可能会让一个以上的消费者感到麻烦。