Почему массив реализует IList?

144

См. Определение класса System.Array

public abstract class Array : IList, ...

Теоретически я смогу написать это и буду счастлив

int[] list = new int[] {};
IList iList = (IList)list;

Я также должен иметь возможность вызывать любой метод из iList

 ilist.Add(1); //exception here

Мой вопрос не в том, почему я получаю исключение, а в том, почему Array реализует IList ?

Алексей
источник
23
Хороший вопрос. Мне никогда не нравилась идея толстых интерфейсов (это технический термин для такого дизайна).
Конрад Рудольф
4
@Henk, см. Blogs.msdn.com/b/bclteam/archive/2004/11/19/267089.aspx
Энтони Пеграм,
2
Кого-нибудь действительно волнует LSP? Мне это кажется довольно академичным.
Гейб,
13
@Gabe, тогда вам нужно работать с большими кодовыми базами. Реализация поведения (наследование от интерфейса), а затем простое игнорирование того, что вам не нравится / не может поддерживать, приводит к вонючему, запутанному, кастингу и, наконец, к ошибочному коду.
Мариус
3
@Gabe - это коллекция, которая подразумевает изменчивость, а не содержащиеся в ней сущности. Вы можете сделать свой класс членом типа, который реализует как IRWList <>, так и IReadList <>, использовать if как IRWList <> внутри вашего класса и выставить его как IReadList. Да, вам нужно куда-то добавить сложность, но я просто не понимаю, как это применимо к игнорированию LSP как очень хорошего принципа проектирования (хотя не знал о свойстве IsReadOnly, которое делает IList более сложным с точки зрения потребителей)
Мариус

Ответы:

97

Поскольку массив обеспечивает быстрый доступ по индексу, и IList/ IList<T>- единственные интерфейсы коллекции, которые это поддерживают. Так что, возможно, ваш настоящий вопрос: «Почему нет интерфейса для постоянных коллекций с индексаторами?» И на это у меня нет ответа.

Для коллекций также нет интерфейсов только для чтения. И мне их не хватает даже больше, чем постоянный размер с интерфейсом индексаторов.

IMO должно быть еще несколько (общих) интерфейсов коллекции в зависимости от функций коллекции. И названия тоже должны были быть другие, Listибо что-то с индексатором действительно тупо ИМО.

  • Просто перечисление IEnumerable<T>
  • Только для чтения, но без индексатора (.Count, .Contains, ...)
  • Изменяемый размер, но без индексатора, т.е. устанавливается как (Добавить, Удалить, ...) текущий ICollection<T>
  • Только для чтения с индексатором (indexer, indexof, ...)
  • Постоянный размер с индексатором (индексатор с сеттером)
  • Переменный размер с индексатором (Insert, ...) текущий IList<T>

Я считаю, что нынешние интерфейсы коллекций - плохой дизайн. Но поскольку у них есть свойства, говорящие вам, какие методы допустимы (и это часть контракта этих методов), это не нарушает принцип подстановки.

Коды
источник
15
Спасибо за ответ. Но я лучше оставлю вопрос как есть. Причина проста. Интерфейс - это публичный контракт. Если кто-то реализует его, он должен полностью реализовать все члены, иначе он нарушает LSP и вообще плохо пахнет, не так ли?
Алексей
17
Это действительно нарушает LSP. Если его не было в list.Add (item), он должен добавить элемент в список независимо от конкретного типа. За исключением исключительных случаев. В реализации массива in выдает исключение в неисключительном случае, что само по себе является плохой практикой
Rune FS
2
@smelch Мне очень жаль, но ты ошибся в LSP. Массив не реализует addи, следовательно, не может быть заменен чем-то, что работает, когда эта способность требуется.
Rune FS
8
Я признаю , что это технически не нарушает LSP только потому , что в документации говорится , вы должны проверить IsFixedSizeи IsReadOnlyсвойства, это , безусловно , нарушает Телль, не проси принцип и принцип наименьшего удивления . Зачем реализовывать интерфейс, если вы собираетесь выбросить исключения для 4 из 9 методов?
Мэтью
12
Прошло некоторое время с момента первоначального вопроса. Но теперь с .Net 4.5 появились дополнительные интерфейсы IReadOnlyList и IReadOnlyCollection .
Тобиас
43

Раздел замечания документации по IListсловам

IList является потомком интерфейса ICollection и является базовым интерфейсом для всех неуниверсальных списков. Реализации IList делятся на три категории: только для чтения, фиксированного размера и переменного размера . Список IList, доступный только для чтения, изменить нельзя. Список IList фиксированного размера не позволяет добавлять или удалять элементы, но позволяет изменять существующие элементы. Список IList переменного размера позволяет добавлять, удалять и изменять элементы.

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

Брайан Расмуссен
источник
5
Думаю, у них в итоге получилось бы много интерфейсов. IListFixedSize, IListReadOnly ...
Магнус
10
that'a на самом деле хороший ответ с точки зрения документации. Но мне это скорее похоже на взлом. Интерфейсы должны быть тонкими и простыми, чтобы класс мог реализовать все члены.
Алексей
1
@oleksii: Согласен. Интерфейсы и исключения времени выполнения - не самая элегантная комбинация. В защиту Arrayэтого Addметода явно реализован метод, что снижает риск его случайного вызова.
Брайан Расмуссен
Пока мы не создадим реализацию, IListкоторая запрещает как модификацию, так и добавление / удаление. Тогда документация больше не верна. : P
Тимо
1
@Magnus - В .Net 4.5 есть дополнительные интерфейсы IReadOnlyList и IReadOnlyCollection .
RBT
17

Потому что не все ILists являются изменяемыми (см. IList.IsFixedSizeИ IList.IsReadOnly), а массивы определенно ведут себя как списки фиксированного размера.

Если ваш вопрос действительно «почему он реализует неуниверсальный интерфейс», то ответ таков: они были до появления дженериков.

пользователь541686
источник
10
@oleksii: Нет, он не нарушает LSP, потому что IList сам интерфейс сообщает вам, что он не может быть изменяемым. Если бы это было на самом деле гарантированно изменчивыми и массив сказал вам иначе, то это нарушило бы правило.
user541686
На самом деле, Array действительно нарушает LSP в случае универсального IList<T>и не нарушает его в случае не общего IList: enterprisecraftsmanship.com/2014/11/22/…
Владимир
5

Это наследие тех времен, когда было непонятно, как работать с коллекциями только для чтения и доступен ли Array только для чтения. В интерфейсе IList есть флаги IsFixedSize и IsReadOnly. Флаг IsReadOnly означает, что коллекцию вообще нельзя изменить, а IsFixedSize означает, что коллекция разрешает модификацию, но не позволяет добавлять или удалять элементы.

Во времена .Net 4.5 было ясно, что для работы с коллекциями только для чтения требуются некоторые «промежуточные» интерфейсы, поэтому IReadOnlyCollection<T> и IReadOnlyList<T>были введены.

Вот отличный пост в блоге, описывающий детали: Коллекции только для чтения в .NET

Владимир
источник
0

Определение интерфейса IList: «Представляет неуниверсальную коллекцию объектов, к которой можно получить индивидуальный доступ по индексу». Массив полностью удовлетворяет этому определению, поэтому должен реализовывать интерфейс. Исключение при вызове метода Add () - «System.NotSupportedException: коллекция имела фиксированный размер», возникшая из-за того, что массив не может увеличивать свою емкость динамически. Его емкость определяется при создании объекта массива.

Meir
источник
0

Наличие массива, реализующего IList (и транзитивно ICollection), упростило движок Linq2Objects, поскольку приведение IEnumerable к IList / ICollection также будет работать для массивов.

Например, Count () заканчивает внутренним вызовом Array.Length, поскольку он приведен к ICollection, а реализация массива возвращает Length.

Без этого движок Linq2Objects не имел бы специальной обработки для массивов и не работал бы ужасно, или им пришлось бы удвоить код, добавив особый режим для массивов (как они это делают для IList). Они, должно быть, решили сделать так, чтобы массив реализовал IList.

Это мой взгляд на «Почему».

Герман Шенфельд
источник
0

Также детали реализации LINQ Последние проверки для IList, если он не реализует список, им потребуются либо 2 проверки, замедляющие все последние вызовы, либо Last на массиве, принимающем O (N)

user1496062
источник