Почему IEnumerable <T>наследуется от IEnumerable? - PullRequest
34 голосов
/ 21 октября 2008

Это может быть старый вопрос: почему IEnumerable<T> наследуется от IEnumerable?

Вот как .NET это делает, но это приносит небольшие проблемы. Каждый раз, когда я пишу класс, реализующий IEumerable<T>, я должен написать две GetEnumerator() функции, одну для IEnumerable<T>, а другую для IEnumerable.

И, IList<T> не наследуется от IList.

Я не знаю, почему IEnumerable<T> спроектирован иначе.

Ответы [ 4 ]

49 голосов
/ 21 октября 2008

Прямо изо рта лошади (Хейлсберг):

В идеале все универсальные интерфейсы сбора (например, ICollection<T>, IList<T>) наследуются от своих неуниверсальных аналогов, так что экземпляры универсального интерфейса могут использоваться как с универсальным, так и неуниверсальным кодом. Например, было бы удобно, если бы IList<T> мог быть передан коду, который ожидает IList.

Как оказалось, единственный общий интерфейс, для которого это возможно, это IEnumerable<T>, потому что только IEnumerable<T> является противоположным вариантом: в IEnumerable<T> параметр типа T используется только в «выходных» позициях ( возвращаемые значения), а не во «входных» позициях (параметрах). ICollection<T> и IList<T> используют T как на входе, так и на выходе, поэтому эти интерфейсы являются инвариантными. (Кроме того, они были бы противоположными, если бы T использовался только во входных позициях, но здесь это не имеет значения.)

<... чик ...>

Итак, чтобы ответить на ваш вопрос, IEnumerable<T> наследует от IEnumerable, потому что может! : -)

17 голосов
/ 21 октября 2008

Ответ для IEnumerable: «потому что может, не затрагивая безопасность типов».

IEnumerable - это интерфейс «только для чтения», поэтому не имеет значения, что универсальная форма более специфична, чем неуниверсальная форма. Вы ничего не нарушаете, реализуя оба. IEnumerator.Current возвращает object, тогда как IEnumerator<T>.Current возвращает T - это нормально, так как вы всегда можете законно преобразовать в object, хотя это может означать бокс.

Сравните это с IList<T> и IList - вы можете позвонить Add(object) на IList, тогда как это может быть недействительным для любого конкретного IList<T> (на самом деле, всего, кроме IList<object>).

1020 * Брэда Абрама в блоге с ответом Андерса на этот самый вопрос.

3 голосов
/ 21 октября 2008

Это для обратной совместимости. Если вы вызываете функцию .Net 1.1, которая ожидает ванильный IEnumerable, вы можете передать свой общий IEnumerable.

К счастью, универсальный IEnumerator наследует старый IEnumerator

Обычно я реализую закрытый метод, который возвращает перечислитель, а затем передаю его для метода GetEnumerator старого и нового стиля.

    private IEnumerator<string> Enumerator() {
        // ...
    }

    public IEnumerator<string> GetEnumerator() {
        return Enumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
        return Enumerator();
    }
2 голосов
/ 21 октября 2008

Это так, что он будет работать с классами, которые не поддерживают дженерики. Кроме того, универсальные .NET не позволяют вам делать такие вещи, как приведение IList к IList , поэтому неуниверсальные версии интерфейсов могут быть весьма полезны, когда вам нужен фиксированный базовый класс или интерфейс.

...