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