If you have a whole sequence of items you want to add to an existing IImmutableQueue<T>
, it’s pretty simple to write a loop to add them all. We can make this even easier by writing a simple extension method that handles the looping on our behalf.
This straightforwardly handles the case where we already have the items to add in some kind of sequence, perhaps a List<T>
.
But what happens if the items we want to add are already in an IImmutableQueue<T>
?
The .EnqueueAll()
method above will iterate through the entire queue, adding each item in turn to the original queue, allocating new instances as it goes. When finished, the original queue will be discarded for the garbage collector to clean up.
This approach doesn’t sound particularly efficient. In fact, it sounds very much like the problem we previously identified with our use of the .Reverse()
method for the _inbound
stack.
What can we do instead?
The key insight here is to observe that we’re essentially making a decision about what happens later: after the code finishes consuming items from this queue, it should consume the items from that queue.
Instead of acting immediately, let’s turn the decision into a class that will do the right thing later on. We’ll call this class ConcatenatedQueue<T>
.
To begin, we need a variation of our extension method to handle enqueuing an entire queue of items by returning an instance of our new class:
We take the opportunity to do a bit of optimization, ensuring that if one or the other queue is empty, we don’t create anything new.
Here’s a partial declaration of ConcatenatedQueue<T>
:
When we .Dequeue()
an item from the front of the queue, we need to discard it from the front of our _outbound
queue; if that’s now empty, we can implement the earlier decision by returning the _buffer
for continued use:
Enumerating through the concatenated queue is straightforward - we first work through the _outbound
queue, then the _buffered
queue.
The remainder of the implementation is straightforward.
It turns out that this idea - of taking a decision about future behaviour and wrapping it into a class to be actioned later - is a particularly powerful one. Much of the power of the LINQ library found in the .NET Framework is based on this idea of deferred execution.
Comments
blog comments powered by Disqus