在我提问之前,让我先说一个显而易见的答案:ICollection<T>
接口包含一个Remove
方法来删除任意元素,而Queue<T>
和Stack<T>
不能真正支持这一点(因为它们只能删除“末尾”元素)。
好的,我明白了。实际上,我的问题不是特别针对Queue<T>
或Stack<T>
集合类型;相反,它关于不为任何本质上是T
值集合的泛型类型实现ICollection<T>
的设计决策。
这里有一点我觉得奇怪。假设我有一个接受任意T
集合的方法,并且出于我正在编写的代码的目的,知道集合的大小会很有用。例如(下面的代码很简单,仅供说明!):
// 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;
}
}
}
现在,这段代码完全可以运行在一个
Queue<T>
或者Stack<T>
上,只是这些类型没有实现ICollection<T>
。当然,它们实现了ICollection
接口——我猜主要是因为Count
属性——但这会导致像下面这样奇怪的优化代码:// 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!");
}
不如完全放弃ICollection接口(当然,我并不是指放弃实现,因为那会破坏向后兼容性;我只是指停止使用它),而是用显式实现来实现ICollection中没有完全匹配的成员。看看ICollection提供了什么:Count--Queue和Stack都有这个;IsReadOnly--Queue和Stack很容易也可以有这个;Add--Queue可以显式实现它(使用Enqueue),Stack也可以(使用Push);Clear、Contains和CopyTo都有;GetEnumerator--当然有;Remove--这是唯一一个Queue和Stack没有完全匹配的。ICollection.Remove返回一个bool,所以Queue可以显式实现Remove并检查要删除的项是否真的是头元素(使用Peek),如果是,则调用Dequeue并返回true,否则返回false。Stack可以使用Peek和Pop轻松地给出类似的实现。既然我已经写了大约一千个字解释为什么我认为这是可能的,那么我要问的显然问题是:为什么Queue和Stack的设计者没有实现这个接口?也就是说,是哪些设计因素(我可能没有考虑到的)导致了这个决定,认为这是错误的选择?为什么要实现ICollection呢?
我想知道在设计自己的类型时,是否有任何指导原则需要考虑与接口实现有关的问题,我可能在提出这个问题时忽略了一些东西。例如,明确实现通常不支持的接口是否只是被认为是不好的实践(如果是这样,那么这似乎就与
List<T>
实现IList
相矛盾)?队列/堆栈的概念和ICollection<T>
所代表的概念是否存在概念上的断开?基本上,我感觉肯定有一个很好的理由,例如
Queue<T>
没有实现ICollection<T>
,我不想在未经通知和充分考虑的情况下盲目地设计自己的类型并以不合适的方式实现接口。我很抱歉这个问题太长了。