Почему (действительно ли)? List <T>реализует все эти интерфейсы, а не только IList <T>? - PullRequest
67 голосов
/ 27 января 2011

List декларация от MSDN:

public class List<T> : IList<T>, ICollection<T>, 
 IEnumerable<T>, IList, ICollection, IEnumerable

Отражатель дает похожую картину. List действительно реализует все это (если да, то почему)? Я проверил:

    interface I1 {}
    interface I2 : I1 {}
    interface I3 : I2 {}

    class A : I3 {}
    class B : I3, I2, I1 {}

    static void Main(string[] args)
    {
        var a = new A();
        var a1 = (I1)a;
        var a2 = (I2)a;
        var a3 = (I3)a;

        var b = new B();
        var b1 = (I1) b;
        var b2 = (I2)b;
        var b3 = (I3)b;
    }

компилируется.

[ОБНОВЛЕНО]:

Ребята, насколько я понимаю, все ответы остаются таковыми:

class Program
{

    interface I1 {}
    interface I2 : I1 {}
    interface I3 : I2 {}

    class A : I3 {}
    class B : I3, I2, I1 {}

    static void I1M(I1 i1) {}
    static void I2M(I2 i2) {}
    static void I3M(I3 i3) {}

    static void Main(string[] args)
    {
        var a = new A();
        I1M(a);
        I2M(a);
        I3M(a);

        var b = new B();
        I1M(b);
        I2M(b);
        I3M(b);

        Console.ReadLine();
    }
}

выдаст ошибку, но он компилируется и работает без ошибок. Почему?

Ответы [ 5 ]

131 голосов
/ 27 января 2011

ОБНОВЛЕНИЕ: Этот вопрос лег в основу моей записи в блоге за понедельник, 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, поэтому он не наследуется от него.

5 голосов
/ 06 апреля 2011

Отличный вопрос и отличный ответ от Эрика Липперта.Этот вопрос приходил мне в голову в прошлом, и следующее мое понимание, надеюсь, это поможет вам.

Предположим, я программист на C # на другой планете во вселенной, на этой планете, все животные могут летать,Итак, моя программа выглядит так:

interface IFlyable 
{
   void Fly();
}
interface IAnimal : IFlyable
{
   //something
}
interface IBird : IAnimal, IFlyable
{
}

Ну, вы можете быть сбиты с толку, поскольку Birds равны Animals, и все Animals могут летать, почему мы должны указать IFlyable в интерфейсеIBird?ОК. Давайте изменим его на:

interface IBird : IAnimal
{
}

Я на 100% уверен, что программа работает как прежде, поэтому ничего не изменилось?IFlyable в интерфейсе IBird абсолютно бесполезен?Давайте продолжим.

Однажды моя компания продала программное обеспечение другой компании на Земле.Но на Земле не все животные могут летать!Поэтому, конечно, нам нужно изменить интерфейс IAnimal и классы, которые его реализуют.После модификации я обнаружил, что IBird теперь неверен, потому что птицы на земле могут летать!Теперь я сожалею об удалении IFlyable из IBird!

5 голосов
/ 27 января 2011

Неуниверсальные интерфейсы для обратной совместимости. Если вы пишете код с использованием обобщений и хотите передать свой список какому-либо модулю, написанному на .NET 1.0 (у которого нет обобщений), вы все равно хотите, чтобы это было успешно. Следовательно IList, ICollection, IEnumerable.

3 голосов
/ 27 января 2011
  1. Да, это так, потому что List<T> может иметь свойства и метод для выполнения всех этих интерфейсов. Вы не знаете, какой интерфейс кто-то объявит как параметр или возвращаемое значение, поэтому, чем больше реализуется список интерфейсов, тем более универсальным он может быть.
  2. Ваш код будет скомпилирован, потому что апкастинг (var a1 = (I1)a;) не выполняется во время выполнения, а не во время компиляции. Я могу сделать var a1 = (int)a и собрать его.
2 голосов
/ 27 января 2011

List<> реализует все эти интерфейсы, чтобы раскрыть функциональность, описанную различными интерфейсами. Он включает в себя функции универсального List, Collection и Enumerable (с обратной совместимостью с неуниверсальными эквивалентами)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...