Прежде чем я даже спрошу, позвольте мне получить очевидный ответ: интерфейс ICollection<T> включает в себя метод Remove для удаления произвольного элемента, который Queue<T> и Stack<T> не могут реально поддерживать ( поскольку они могут удалять только "конечные" элементы).
Хорошо, я это понимаю. На самом деле, мой вопрос касается не только типов коллекций Queue<T> или Stack<T>; скорее, о конструктивном решении не внедрять ICollection<T> для любого родового типа, который по существу представляет собой набор значений 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<T> с явной реализацией для членов, которые не имеют идеального соответствия?
Я имею в виду, посмотрите, что предлагает ICollection<T>:
-
Count-Queue<T>иStack<T>оба имеют это. -
IsReadOnly-Queue<T>иStack<T>легко могли иметь это. -
Add-Queue<T>может реализовать это явно (с помощьюEnqueue), как иStack<T>(сPush). -
Clear- Проверьте. -
Contains- Проверьте. -
CopyTo- Проверьте. -
GetEnumerator- Проверить (duh). -
Remove- Это единственное, чтоQueue<T>иStack<T>не имеют идеального соответствия.
И вот настоящий кикер: ICollection<T>.Remove возвращает a bool; поэтому явная реализация для Queue<T> может полностью (например) проверить, является ли элемент, который нужно удалить, фактически элементом head (с использованием Peek), и если да, вызовите Dequeue и верните true, в противном случае верните false. Stack<T> может быть легко предоставлена аналогичная реализация с Peek и Pop.
Хорошо, теперь, когда я написал около тысячи слов о том, почему я думаю, что это было бы возможно, я задаю очевидный вопрос: почему разработчики Queue<T> и Stack<T> не реализовали это интерфейс? То есть, каковы были конструктивные факторы (которые я, вероятно, не рассматриваю), которые привели к решению, что это будет неправильный выбор? Почему вместо этого был реализован ICollection?
Мне интересно, при разработке моих собственных типов есть какие-то руководящие принципы, которые я должен рассмотреть в отношении реализации интерфейса, которые я мог бы игнорировать при задании этого вопроса. Например, просто считается, что плохая практика явно реализует интерфейсы, которые не полностью поддерживаются вообще (если это так, похоже, это противоречит, например, List<T> реализации IList)? Существует ли концептуальное отключение концепции очереди/стека и что ICollection<T> предназначен для представления?
В принципе, я чувствую, что должна быть довольно веская причина. Queue<T> (например) не реализует ICollection<T>, и я не хочу просто идти слепо вперед, разрабатывая свои собственные типы и реализуя интерфейсы в неуместным образом, не будучи информированным и полностью не понимая, что я делаю.
Я извиняюсь за сверхдолговечный вопрос.