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

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();
    }
}

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

Ответ 1

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

Ответ 2

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

Ответ 3

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

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

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

Ну, вы можете быть смущены, так как Birds - Animals, и все Animals могут летать, зачем нам указывать IFlyable в интерфейсе IBird? OK Позвольте изменить его на:

interface IBird : IAnimal
{
}

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

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

Ответ 4

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

Ответ 5

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