ОБНОВЛЕНИЕ: Этот вопрос лег в основу моей записи в блоге за понедельник, 4 апреля 2011 года . Спасибо за отличный вопрос.
Позвольте мне разбить его на множество мелких вопросов.
Реализует ли List<T>
все эти интерфейсы?
Да.
Почему?
Поскольку, когда интерфейс (скажем, IList<T>
) наследует от интерфейса (скажем, IEnumerable<T>
), тогда разработчики более производного интерфейса также обязаны реализовывать менее производный интерфейс. Вот что означает наследование интерфейса; если вы выполняете контракт с более производным типом, то вам также необходимо выполнить контракт с менее производным типом.
Итак, нужен ли класс для реализации всех методов всех интерфейсов при транзитивном замыкании его базовых интерфейсов?
Точно.
Требуется ли классу, реализующему более производный интерфейс, указать в своем списке базовых типов, что он реализует все эти менее производные интерфейсы?
номер
Требуется ли классу НЕ указывать это?
номер
Так что необязательно указываются ли менее производные реализованные интерфейсы в списке базовых типов?
Да.
Всегда
Почти всегда:
interface I1 {}
interface I2 : I1 {}
interface I3 : I2 {}
Необязательно, заявляет ли I3, что он наследуется от I1.
class B : I3 {}
Разработчики I3 обязаны реализовывать I2 и I1, но они не обязаны явно указывать, что они делают это. Это необязательно.
class D : B {}
Производные классы не обязаны повторно указывать, что они реализуют интерфейс из своего базового класса, но им разрешено это делать. (Этот случай особенный; подробности см. Ниже.)
class C<T> where T : I3
{
public virtual void M<U>() where U : I3 {}
}
Аргументы типа, соответствующие T и U, требуются для реализации I2 и I1, но для ограничений T или U указывать это необязательно.
Всегда необязательно повторно указывать любой базовый интерфейс в частичном классе:
partial class E : I3 {}
partial class E {}
Во второй половине E разрешено указывать, что он реализует I3 или I2 или I1, но не обязан это делать.
ОК, я понял; это необязательно. Зачем кому-то излишне указывать базовый интерфейс?
Возможно, потому что они считают, что это делает код более легким для понимания и более самодокументируемым.
Или, возможно, разработчик написал код как
interface I1 {}
interface I2 {}
interface I3 : I1, I2 {}
и понял, о, подожди минутку, I2 должен наследовать от I1. Почему для этого редактирования требуется, чтобы разработчик вернулся и изменил объявление I3 на , а не , содержащее явное упоминание I1? Я не вижу причин заставлять разработчиков удалять избыточную информацию.
Помимо того, что легче читать и понимать, есть ли техническая разница между указанием интерфейса явно в списке базовых типов и оставлением его неустановленным, но подразумеваемым?
Обычно нет, но в одном случае может быть небольшая разница. Предположим, у вас есть производный класс D, в базовом классе которого реализованы некоторые интерфейсы. D автоматически реализует эти интерфейсы через B. Если вы переопределите интерфейсы в списке базовых классов D, то компилятор C # выполнит повторную реализацию интерфейса . Детали немного тонкие; если вас интересует, как это работает, тогда я рекомендую внимательно прочитать раздел 13.4.6 спецификации C # 4.
Действительно ли исходный код List<T>
содержит все эти интерфейсы?
Нет. Фактический исходный код говорит
public class List<T> : IList<T>, System.Collections.IList
Почему MSDN имеет полный список интерфейсов, а реальный исходный код - нет?
Потому что MSDN - это документация;он должен дать вам столько информации, сколько вы захотите.Гораздо понятнее, чтобы документация была завершена в одном месте, чем поиск по десяти различным страницам, чтобы выяснить, что такое полный набор интерфейсов.
Почему Reflector отображает весь список?
Отражатель имеет только метаданные для работы.Поскольку указывать полный список необязательно, Reflector не знает, содержит ли исходный исходный код полный список или нет.Лучше ошибиться на стороне дополнительной информации.Опять же, Reflector пытается помочь вам, показывая вам больше информации, чем скрывая информацию, которая вам может понадобиться.
БОНУСНЫЙ ВОПРОС: Почему IEnumerable<T>
наследуется от IEnumerable
, а IList<T>
не наследуетсяс IList
?
Последовательность целых чисел может рассматриваться как последовательность объектов, заключая в квадрат каждое целое число, которое выходит из последовательности.Но список целых чисел для чтения и записи не может рассматриваться как список объектов для чтения и записи, потому что вы можете поместить строку в список объектов для чтения и записи.IList<T>
не требуется для выполнения всего контракта IList
, поэтому он не наследуется от него.