Почему (действительно ли?) Список реализации всех этих интерфейсов, а не только IList?


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 66

5 ответов:

обновление: этот вопрос был основой Моя запись в блоге за понедельник 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, базовый класс B которого реализовал некоторые интерфейсы. D автоматически реализует эти интерфейсы через B. Если вы повторно укажете интерфейсы в списке базовых классов D, то компилятор C# сделает повторная реализация интерфейса. Детали немного тонкие; если вы заинтересованы в том, как это работает, то я рекомендую внимательно прочитать раздел 13.4.6 спецификации C# 4.

тут List<T> исходный код на самом деле состояние всех этих интерфейсов?

нет. Фактический исходный код говорит

public class List<T> : IList<T>, System.Collections.IList

почему MSDN имеет полный список интерфейсов, но реальный исходный код нет?

потому что в MSDN документация; он должен дать вам столько информации, сколько вы захотите. Это гораздо более ясно для документации, чтобы быть полным все в одном месте, чем заставить вас искать через десять различных страниц, чтобы узнать, что полный набор интерфейса.

почему рефлектор показывает все список?

отражатель имеет только метаданные для работы. Поскольку ввод полного списка является необязательным, Reflector понятия не имеет, содержит ли исходный исходный код полный список или нет. Лучше ошибиться на стороне более подробной информации. Опять же, Reflector пытается помочь вам, показывая вам больше информации, а не скрывая информацию, которая вам может понадобиться.

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

последовательность целых чисел можно рассматривать как последовательность объектов, помещая каждое целое число в бокс, когда оно выходит из последовательности. Но список целых чисел для чтения и записи нельзя рассматривать как список объектов для чтения и записи, потому что вы можете поместить строку в список объектов для чтения и записи. Ан IList<T> не требуется выполнять весь контракт IList, поэтому он не наследует от него.

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

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

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

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

Ну вы можете быть смущены, поскольку Birds are Animals, а всего Animals может летать, зачем нам необходимо указать IFlyable в окно IBird? Хорошо, давайте изменим его к:

interface IBird : IAnimal
{
}

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

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

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

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