Почему Queue (T) и Stack (T) не реализуют ICollection (T)?

Прежде чем я даже задам вопрос, позвольте мне получить очевидный ответ: Интерфейс 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 .Remove возвращает bool ; поэтому явная реализация для Queue может полностью (например) проверить, является ли удаляемый элемент на самом деле элементом заголовка (используя Peek ), и если это так, вызовите Dequeue и верните true , в противном случае верните false . Stack может легко получить аналогичную реализацию с Peek и Pop .

Хорошо, теперь, когда я ' Написав около тысячи слов о том, почему я думаю, что это возможно, я задаю очевидный вопрос: почему не разработчики Queue и Stack реализуют этот интерфейс? То есть, какие факторы проектирования (которые я, вероятно, не рассматриваю) привели к решению, что это был бы неправильный выбор? Почему вместо этого была реализована ICollection ?

Мне интересно, есть ли при разработке моих собственных типов какие-либо руководящие принципы, которые я должен учитывать в отношении реализации интерфейса, которые я мог бы упустить задавая этот вопрос. Например, считается ли плохой практикой явная реализация интерфейсов, которые не полностью поддерживаются в целом (если да, это может противоречить, например, List , реализующему IList )? Есть ли концептуальное несоответствие между концепцией очереди / стека и тем, что ICollection должно представлять?

В принципе, я чувствую, что должно быть довольно хорошее причина Queue (например) не реализует ICollection , и я не хочу просто слепо идти вперед, создавая свой собственный типы и реализация интерфейсов ненадлежащим образом, не будучи информированным и полностью обдумав, что я делаю.

Прошу прощения за сверхдлинный вопрос.

15
задан Dan Tao 23 January 2011 в 20:12
поделиться