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

#c# #arrays #ilist #liskov-substitution-principle

#c# #массивы #ilist #принцип подстановки Лискова

Вопрос:

Смотрите определение System.Класс Array

 public abstract class Array : IList, ...
  

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

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

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

  ilist.Add(1); //exception here
  

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

Комментарии:

1. Хороший вопрос. Мне никогда не нравилась идея толстых интерфейсов (это технический термин для такого рода дизайна).

2. @Хенк, видишь: blogs.msdn.com/b/bclteam/archive/2004/11/19/267089.aspx

3. Кого-нибудь действительно волнует LSP? Мне это кажется довольно академичным.

4. @Gabe, тогда тебе нужно работать с большими кодовыми базами. Реализация поведения (наследуемого от интерфейса), а затем простое игнорирование того, что вам не нравится / не может поддерживаться, приводит к вонючему, запутанному, приведению и, наконец, к ошибочному коду.

5. @Gabe — это коллекция, которая подразумевает изменчивость, а не содержащиеся в ней объекты. Вы можете сделать свой класс членом типа, который реализует как IRWList<>, так и IReadList<> , использовать if как IRWList<> внутри вашего класса и предоставлять его как IReadList. Да, вы должны где-то усложнять, но я просто не понимаю, как это применимо к игнорированию LSP как очень хорошего принципа проектирования (хотя не знал о свойстве IsReadOnly, которое делает IList более сложным с точки зрения потребителей)

Ответ №1:

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

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

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

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

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

Комментарии:

1. спасибо за ответ. Но я предпочел бы оставить вопрос как есть. Причина проста. Интерфейс — это публичный контракт. Если кто-то реализует это, нужно полностью реализовать все элементы, иначе это нарушает LSP и вообще плохо пахнет, не так ли?

2. Это действительно нарушает LSP. Если он не указан в списке.Add(item) должен добавить элемент в список независимо от конкретного типа. За исключением исключительных случаев. В реализации array в выдает исключение в случае, не являющемся исключением, что само по себе является плохой практикой

3. @smelch Прошу прощения, но тогда вы неправильно поняли LSP. Массив не реализует add и, следовательно, не может быть заменен чем-то, что выполняет, когда требуется эта способность.

4. Я признаю, что это технически не нарушает LSP только потому, что в документации указано, что вы должны проверить свойства IsFixedSize и IsReadOnly , это определенно нарушает принцип «Скажи, не спрашивай» и принцип наименьшего удивления . Зачем реализовывать интерфейс, когда вы просто собираетесь создавать исключения для 4 из 9 методов?

5. С момента первоначального вопроса прошло некоторое время. Но теперь с .Net 4.5 появились дополнительные интерфейсы IReadOnlyList и IReadOnlyCollection .

Ответ №2:

В разделе замечаний документации для IList говорится:

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

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

Комментарии:

1. Я предполагаю, что в итоге у них было бы много интерфейсов. IListFixedSize, IListReadOnly…

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

3. @oleksii: Я согласен. Интерфейсы и исключения во время выполнения — не самая элегантная комбинация. В защиту того, что Array он реализует Add метод явно, что снижает риск его случайного вызова.

4. Пока мы не создадим реализацию, IList которая запрещает как модификацию, так и добавление / удаление. Тогда документация больше не верна. 😛

5. @Magnus — В .Net 4.5 есть дополнительные интерфейсы IReadOnlyList и IReadOnlyCollection .

Ответ №3:

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

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

Комментарии:

1.@oleksii: Нет, это не нарушает LSP, потому что IList сам интерфейс сообщает вам, что он не может быть изменяемым. Если бы на самом деле было гарантировано, что он будет изменяемым, а массив сказал вам обратное, тогда это нарушило бы правило.

2. На самом деле, Array действительно нарушает LSP в случае универсального IList<T> и не нарушает его в случае не общего IList : enterprisecraftsmanship.com/2014/11/22 /…

Ответ №4:

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

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

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

Комментарии:

1. Массив доступен не только для чтения, вы можете просто задать значения в индексах. Массив просто имеет фиксированный размер, но там нет IFixedSizeList .

Ответ №5:

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

Ответ №6:

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

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

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

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

Ответ №7:

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

Ответ №8:

Array Это всего лишь одна из многих возможных реализаций IList .

Поскольку код должен быть слабо связанным, зависеть от абстракций, а что нет… Вызывается конкретная реализация IList , которая использует последовательную память (массив) для хранения своих значений Array . Мы не «добавляем» IList к Array классу, это просто неправильный порядок рассуждений; Array реализует IList как массив.

Исключение — это именно то, что определяет интерфейс. Неудивительно, если вы знаете весь интерфейс, а не только один метод. Интерфейс также дает вам возможность проверить IsFixedSize свойство и посмотреть, безопасно ли вызывать Add метод.