Прежде чем я даже задам вопрос, позвольте мне получить очевидный ответ: Интерфейс 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>
. Они, конечно, do реализуют 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
- Проверить (дух).
Remove
- Это единственный, для которого Queue<T>
и Stack<T>
не имеют идеального соответствия.
А вот настоящий кикер: ICollection<T>.Remove
возвращает bool
; поэтому явная реализация для Queue<T>
может полностью (например) проверить, является ли удаляемый элемент на самом деле элементом head (с использованием Peek
), и, если это так, вызвать Dequeue
и вернуть true
, в противном случае вернуть false
. Stack<T>
можно легко получить аналогичную реализацию с Peek
и Pop
.
Хорошо, теперь, когда я написал около тысячи слов о том, почему Я думаю, что это возможно, я задаю очевидный вопрос: почему не дизайнеры Queue<T>
и Stack<T>
реализуют этот интерфейс? То есть, какие факторы проектирования (которые я, вероятно, не рассматриваю) привели к решению, что это будет неправильный выбор? Почему вместо этого был реализован ICollection
? 1105 *
Мне интересно, есть ли при разработке моих собственных типов какие-либо руководящие принципы, которые я должен учитывать в отношении реализации интерфейса, которые я мог бы не заметить, задавая этот вопрос. Например, считается ли просто плохой практикой явная реализация интерфейсов, которые не полностью поддерживаются в целом (если это так, то это может противоречить, например, List<T>
реализации IList
)? Существует ли концептуальное разъединение между концепцией очереди / стека и тем, что ICollection<T>
должно представлять?
По сути, я чувствую, что должна быть довольно веская причина Queue<T>
(например) не реализует ICollection<T>
, и я не хочу просто слепо идти вперед, разрабатывая свою собственную несоответствующим образом вводит и реализует интерфейсы, не будучи информированным и полностью продумывая, что я делаю.
Я прошу прощения за сверхдлинный вопрос.