Прежде чем я даже спрошу, позвольте мне получить очевидный ответ: Интерфейс ICollection<T>
включает в себя метод Remove
для удаления произвольного элемента, который Queue<T>
и Stack<T>
не могут действительно поддерживать (поскольку они могут только удалить «конец», элементы).Почему Queue (T) и Stack (T) не реализуют ICollection (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
возвращает bool
; поэтому явная реализация для Queue<T>
может полностью (например) проверить, если элемент, который будет удален фактически головной элемент (с использованием Peek
), и если да, вызовите Dequeue
и вернуть true
, в противном случае вернуть false
. Stack<T>
может быть легко предоставлена аналогичная реализация с Peek
и Pop
.
Хорошо, теперь, когда я написал около тысячи слов о том, почему я думаю, что это было бы возможно, я задаю этот очевидный вопрос: почему не конструкторы Queue<T>
и Stack<T>
реализовать этот интерфейс ? То есть, каковы были конструктивные факторы (которые я, вероятно, не рассматриваю), которые привели к решению, что это будет неправильный выбор? Почему вместо этого был выполнен ICollection
?
Мне интересно, при разработке моих типов есть какие-либо руководящие принципы, которые я должен рассмотреть в отношении реализации интерфейса, которые я мог бы игнорировать при задании этого вопроса. Например, просто считается, что плохая практика явно реализует интерфейсы, которые в полной мере не поддерживаются вообще (если это так, это может противоречить, например, List<T>
, реализующим IList
)? Есть ли концептуальный отсоединить между концепцией очереди/стека и что означает ICollection<T>
?
В принципе, я чувствую, что должна быть очень хорошая причина Queue<T>
(к примеру) не реализовать ICollection<T>
, и я не хочу, чтобы просто идти слепо вперед разработку своих собственных типов и реализации интерфейсов в неподходящий без осознания и полного размышления о том, что я делаю.
Приносим извинения за сверхтяжелый вопрос.
Еще один пример бедных библиотек сбора МС. Эта проблема могла быть решена, по крайней мере, путем разделения интерфейсов на версии только для чтения и чтения-записи (с версией для чтения и записи, наследующей от версии только для чтения), а также с более мелкозернистыми интерфейсами, фокусируясь только на конкретный аспект коллекции, а не включать нерелевантные свойства и методы. Поэтому ICollection не должен включать в себя Добавить, Очистить или Удалить. Это должно было быть оставлено, например, для IMutableCollection . ICollection может быть реализован более многими классами. –
siride
@siride: Ваш взгляд на библиотеки коллекций MS отражает мои. Очень неприятно, что такие вещи, как индексированные свойства, должны быть определены дважды для версий только для чтения и чтения-записи, но это жизнь. – supercat
+1 для инноваций для реализации 'Remove' в одиночку, это было здорово :) И нет, этот супер-длинный q хорошо написан. – nawfal