Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should an IEnumerable iterator on a Queue dequeue an item

I have created a custom generic queue which implements a generic IQueue interface, which uses the generic Queue from the System.Collections.Generic namespace as a private inner queue. Example has been cleaned of irrelevant code.

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

I want to keep things consistent with the core implementations and have noticed that the core Queue implements IEnumerable so I will do the same either by explicitly implementing IEnumerable on the class or inheriting it with the IQueue interface.

What I want to know is when enumerating over the queue should each move next dequeue the next item? I have used reflector to see how Microsoft has done it and all they do is step through the queues private array but Microsoft is far from infallible so I wanted to get a general opinion.

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

    ...
}

I am in two minds, on one hand I feel that iterating through a collection should not changed the collections state but on the other hand and especially with my particular implementation it would make the usage look clean.

EDIT

To put things into context. The class that I am implementing does a Monitor.Wait when Dequeuing and there are no items in the queue. When an item is put onto the queue there is a Monitor.Pulse. This allows for one thread to push stuff onto the queue and the other to essentially "watch" the queue.

From a coding point of view I am trying to decide which it looks cleaner:

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

//Or

while(systemIsRunning)
{
    DoSomethingWithThe(queue.Dequeue());
}

For my particular implementation it wouldn't matter if there were multiple process dequeuing items. Because it is a queue they can both pick of an item as no item should be processed more than once, hence the use of a queue.

EDIT

Interestingly enough I have found a blog post where someone has done exactly this.

Link

EDIT

One last stab at this before I close this off. How do people feel about the class not implement IEnumerable but having an IEnumerator GetEnumerator() method that dequeues items? The .net language supports duck typing, foreach being one of the uses. Perhaps this deserves it's own question?

EDIT

Have raised the question of implementing a GetEnumerator method without implementing IEnumerable in another question.

like image 547
Bronumski Avatar asked Nov 15 '10 22:11

Bronumski


1 Answers

Iterators should always be idempotent, that is, don't modify the queue as you iterate over it.

There are no guarantees that there won't be two concurrent iterations...


Edit to address your new comments:

When another programmer (such as your future self ;) ) comes along to add features to the code, they may not assume that the iterators are single-use. They might add a log statement that lists what's in the queue just before using it (oops).

Another thing I just thought of is that the visual studio debugger will often enumerate your classes for display. That would cause some extremely confusing bugs :)

If you're implementing a sub-interface of IEnumerable, and don't want to support IEnumerable, you should throw a NotSupportedException. Although this will not give you any compile time warnings, the run time error will be very clear, while a strange IEnumerable implementation could waste future you hours.

like image 110
Rob Fonseca-Ensor Avatar answered Sep 20 '22 14:09

Rob Fonseca-Ensor