Before I even ask, let me get the obvious answer out of the way: The ICollection<T>
interface includes a Remove
method to remove an arbitrary element, which Queue<T>
and Stack<T>
can't really support (since they can only remove "end" elements).
OK, I realize that. Actually, my question is not specifically about the Queue<T>
or Stack<T>
collection types; rather, it's about the design decision of not implementing ICollection<T>
for any generic type that is essentially a collection of T
values.
Here's what I find odd. Say I have a method that accepts an arbitrary collection of T
, and for the purpose of the code I'm writing it would be useful to know the size of the collection. For example (the below code is trivial, for illustration only!):
// Argument validation omitted for brevity.
static IEnumerable<T> FirstHalf<T>(this ICollection<T> source)
{
int i = 0;
foreach (T item in source)
{
yield return item;
if ((++i) >= (source.Count / 2))
{
break;
}
}
}
Now, there's really no reason why this code couldn't operate on a Queue<T>
or a Stack<T>
, except that those types don't implement ICollection<T>
. They do implement ICollection
, of course—I'm guessing mainly for the Count
property alone—but that leads to weird optimization code like this:
// OK, so to accommodate those bastard Queue<T> and Stack<T> types,
// we will just accept any IEnumerable<T>...
static IEnumerable<T> FirstHalf<T>(this IEnumerable<T> source)
{
int count = CountQuickly<T>(source);
/* ... */
}
// Then, assuming we've got a collection type with a Count property,
// we'll use that...
static int CountQuickly<T>(IEnumerable collection)
{
// Note: I realize this is basically what Enumerable.Count already does
// (minus the exception); I am just including it for clarity.
var genericColl = collection as ICollection<T>;
if (genericColl != null)
{
return genericColl.Count;
}
var nonGenericColl = collection as ICollection;
if (nonGenericColl != null)
{
return nonGenericColl.Count;
}
// ...or else we'll just throw an exception, since this collection
// can't be counted quickly.
throw new ArgumentException("Cannot count this collection quickly!");
}
Wouldn't it make more sense to just abandon the ICollection
interface completely (I don't mean drop the implementation, of course, as that would be a breaking change; I just mean, stop using it), and simply implement ICollection<T>
with explicit implementation for members that don't have a perfect match?
I mean, look at what ICollection<T>
offers:
Count
--Queue<T>
andStack<T>
both have this.IsReadOnly
--Queue<T>
andStack<T>
easily could have this.Add
--Queue<T>
could implement this explicitly (withEnqueue
), as couldStack<T>
(withPush
).Clear
-- Check.Contains
-- Check.CopyTo
-- Check.GetEnumerator
-- Check (duh).Remove
-- This is the only one thatQueue<T>
andStack<T>
don't have a perfect match for.
And here's the real kicker: ICollection<T>.Remove
returns a bool
; so an explicit implementation for Queue<T>
could totally (for example) check if the item to be removed is actually the head element (using Peek
), and if so, call Dequeue
and return true
, otherwise return false
. Stack<T>
could easily be given a similar implementation with Peek
and Pop
.
All right, now that I've written about a thousand words on why I think this would be possible, I pose the obvious question: why didn't the designers of Queue<T>
and Stack<T>
implement this interface? That is, what were the design factors (which I am probably not considering) that led to the decision that this would be the wrong choice? Why was ICollection
implemented instead?
I am wondering if, in designing my own types, there are any guiding principles I should consider with respect to interface implementation that I might be overlooking in asking this question. For example, is it just considered bad practice to explicitly implement interfaces that aren't fully supported in general (if so, this would seem to conflict with, e.g., List<T>
implementing IList
)? Is there a conceptual disconnect between the concept of a queue/stack and what ICollection<T>
is meant to represent?
Basically, I sense that there must be a pretty good reason Queue<T>
(for example) doesn't implement ICollection<T>
, and I don't want to just go blindly forward designing my own types and implementing interfaces in an inappropriate manner without being informed and fully thinking through what I'm doing.
I do apologize for the super-long question.