Вопрос

Может ли кто-нибудь объяснить мне, почему я хотел бы использовать IList вместо List в С#?

Связанный вопрос: Почему считается плохим разоблачать List<T>

Это было полезно?

Решение

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

Если вы просто используете его для внутренних целей, вам может быть все равно, и использование List<T> может быть ок.

Другие советы

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

Архитектура Астронавты.Шансы, что вы когда-нибудь напишете свой собственный IList, который добавит что-нибудь к тем, которые уже есть в .NET framework, настолько малы, что это теоретические желейные конфеты, зарезервированные для «лучших практик».

Software astronauts

Очевидно, если вас спрашивают, какой из них вы используете на собеседовании, вы говорите IList, улыбаетесь, и оба выглядят довольными тем, что вы такие умные.Или для общедоступного API — IList.Надеюсь, вы поняли мою точку зрения.

Интерфейс — это обещание (или договор).

Как всегда с обещаниями - меньше, тем лучше.

Некоторые люди говорят: «Всегда используйте IList<T> вместо List<T>".
Они хотят, чтобы вы изменили сигнатуры своих методов с void Foo(List<T> input) к void Foo(IList<T> input).

Эти люди ошибаются.

Это более тонко, чем это.Если вы возвращаете IList<T> как часть общедоступного интерфейса вашей библиотеки, вы оставляете себе интересные возможности, чтобы, возможно, создать собственный список в будущем.Возможно, вам никогда не понадобится эта опция, но это аргумент.Я думаю, что это весь аргумент в пользу возврата интерфейса вместо конкретного типа.Стоит отметить, но в данном случае у него есть серьезный недостаток.

В качестве незначительного контраргумента вы можете обнаружить, что каждому звонящему нужен List<T> в любом случае, и вызывающий код завален .ToList()

Но гораздо важнее то, если вы принимаете IList в качестве параметра, вам лучше быть осторожным, потому что IList<T> и List<T> не веди себя так же.Несмотря на схожесть названий и несмотря на то, что они разделяют интерфейс они делают нет выставить то же самое договор.

Предположим, у вас есть этот метод:

public Foo(List<int> a)
{
    a.Add(someNumber);
}

Полезный коллега «рефакторит» метод, чтобы принять IList<int>.

Ваш код теперь не работает, потому что int[] реализует IList<int>, но имеет фиксированный размер.Контракт на ICollection<T> (основа IList<T>) требует код, который использует его для проверки IsReadOnly перед попыткой добавить или удалить элементы из коллекции.Контракт на List<T> не.

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

Такое ощущение, что это нарушает принцип подстановки Лискова.

 int[] array = new[] {1, 2, 3};
 IList<int> ilist = array;

 ilist.Add(4); // throws System.NotSupportedException
 ilist.Insert(0, 0); // throws System.NotSupportedException
 ilist.Remove(3); // throws System.NotSupportedException
 ilist.RemoveAt(0); // throws System.NotSupportedException

Но это не так.Ответ на этот вопрос заключается в том, что в примере неправильно использован IList<T>/ICollection<T>.Если вы используете ICollection<T>, вам необходимо проверить флаг IsReadOnly.

if (!ilist.IsReadOnly)
{
   ilist.Add(4);
   ilist.Insert(0, 0); 
   ilist.Remove(3);
   ilist.RemoveAt(0);
}
else
{
   // what were you planning to do if you were given a read only list anyway?
}

Если кто-то передает вам массив или список, ваш код будет работать нормально, если вы каждый раз будете проверять флаг и иметь запасной вариант...Но действительно;кто так делает?Разве вы не знаете заранее, нужен ли вашему методу список, который может принимать дополнительных членов;разве вы не указываете это в сигнатуре метода?Что именно вы собирались делать, если вам передали список только для чтения, например int[]?

Вы можете заменить List<T> в код, который использует IList<T>/ICollection<T> правильно.Ты не могу гарантировать, что вы можете заменить IList<T>/ICollection<T> в код, который использует List<T>.

Во многих аргументах в пользу использования абстракций вместо конкретных типов содержится апелляция к принципу единой ответственности/принципу разделения интерфейса — в зависимости от максимально узкого интерфейса.В большинстве случаев, если вы используете List<T> и вы думаете, что вместо этого можно было бы использовать более узкий интерфейс - почему бы и нет IEnumerable<T>?Часто это лучше подходит, если вам не нужно добавлять элементы.Если вам нужно добавить в коллекцию, используйте конкретный тип, List<T>.

Для меня IList<T>ICollection<T>) — худшая часть платформы .NET. IsReadOnly нарушает принцип наименьшего неожиданности.Класс, например Array, который никогда не позволяет добавлять, вставлять или удалять элементы, не должен реализовывать интерфейс с методами Add, Insert и Remove.(смотрите также https://softwareengineering.stackexchange.com/questions/306105/implementing-an-interface-when-you-dont-need-one-of-the-properties)

Является IList<T> подходит для вашей организации?Если коллега просит вас изменить сигнатуру метода для использования IList<T> вместо List<T>, спросите их, как они добавляют элемент в IList<T>.Если они не знают о IsReadOnly (и большинство людей этого не делают), то не используйте IList<T>.Всегда.


Обратите внимание, что флаг IsReadOnly поступает из ICollection<T> и указывает, можно ли добавлять или удалять элементы из коллекции;но, чтобы действительно запутать ситуацию, здесь не указано, можно ли их заменить, что возможно в случае с массивами (которые возвращают IsReadOnlys == true).

Дополнительные сведения об IsReadOnly см. msdn определение ICollection<T>.IsReadOnly

List<T> это конкретная реализация IList<T>, который представляет собой контейнер, к которому можно обращаться так же, как к линейному массиву T[] используя целочисленный индекс.Когда вы укажете IList<T> в качестве типа аргумента метода вы указываете только, что вам нужны определенные возможности контейнера.

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

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

Кроме того, вы можете рассмотреть возможность предоставления минимальной функциональности, которая вам нужна.Например.если вам не нужно изменять содержимое списка, вам, вероятно, следует рассмотреть возможность использования IEnumerable<T>, который IList<T> расширяется.

Я бы немного перевернул вопрос: вместо того, чтобы обосновывать, почему вам следует использовать интерфейс вместо конкретной реализации, попытайтесь обосновать, почему вы будете использовать конкретную реализацию, а не интерфейс.Если вы не можете это оправдать, используйте интерфейс.

IList<T> — это интерфейс, позволяющий наследовать другой класс и при этом реализовать IList<T>, в то время как наследование List<T> не позволяет вам это сделать.

Например, если существует класс A и ваш класс B наследует его, вы не можете использовать List<T>

class A : B, IList<T> { ... }

Принцип TDD и ООП обычно заключается в программировании интерфейса, а не реализации.

В этом конкретном случае, поскольку вы, по сути, говорите о языковой конструкции, а не о специальной, это обычно не имеет значения, но, скажем, например, вы обнаружили, что List не поддерживает то, что вам нужно.Если бы вы использовали IList в остальной части приложения, вы могли бы расширить List своим собственным классом и при этом иметь возможность передавать его без рефакторинга.

Затраты на это минимальны, почему бы не избавить себя от головной боли в дальнейшем?В этом и заключается принцип интерфейса.

public void Foo(IList<Bar> list)
{
     // Do Something with the list here.
}

В этом случае вы можете передать любой класс, реализующий интерфейс IList<Bar>.Если вместо этого вы использовали List<Bar>, можно было передать только экземпляр List<Bar>.

Способ IList<Bar> более слабо связан, чем способ List<Bar>.

Самый важный случай использования интерфейсов вместо реализаций — это параметры вашего API.Если ваш API принимает параметр List, то любой, кто его использует, должен использовать List.Если тип параметра — IList, то вызывающая сторона имеет гораздо больше свободы и может использовать классы, о которых вы никогда не слышали, которых, возможно, даже не существовало на момент написания вашего кода.

Удивительно, но ни в одном из этих вопросов (или ответов) List и IList не упоминается разница в подписи.(Вот почему я искал этот вопрос на SO!)

Итак, вот методы, содержащиеся в List, которых нет в IList, по крайней мере, начиная с .NET 4.5 (около 2015 г.).

  • Аддранже
  • AsReadOnly
  • Бинарный поиск
  • Емкость
  • КонвертироватьВсе
  • Существует
  • Находить
  • Найти все
  • НайтиИндекс
  • Найти последний
  • НайтиLastIndex
  • Для каждого
  • GetRange
  • Инсертанже
  • ЛастИндексОф
  • Убрать все
  • Удалитьдиапазон
  • Обеспечить регресс
  • Сортировать
  • Томассив
  • ОбрезатьИзбыток
  • TrueForAll

Что, если .NET 5.0 заменит System.Collections.Generic.List<T> к System.Collection.Generics.LinearList<T>..NET всегда владеет именем List<T> но они это гарантируют IList<T> это контракт.Так что, ИМХО, мы (по крайней мере, я) не должны использовать чье-то имя (хотя в данном случае это .NET) и потом сталкиваться с проблемами.

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

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

IList<T> defines those methods (not including extension methods)

IList<T> Ссылка MSDN

  1. Добавлять
  2. Прозрачный
  3. Содержит
  4. Скопировать в
  5. GetEnumerator
  6. Индекс
  7. Вставлять
  8. Удалять
  9. Удалитьат

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

List<T> Ссылка MSDN

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

Возможно, вам понадобится IOrderRepository, определяющий коллекцию заказов в IList или ICollection.Затем вы можете иметь различные виды реализаций для предоставления списка заказов, если они соответствуют «правилам», определенным вашим IList или ICollection.

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

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

Согласно совету другого автора, IList<> почти всегда предпочтительнее, однако обратите внимание: есть ошибка в .NET 3.5 sp 1 при запуске IList<> через более чем один цикл сериализации/десериализации с помощью WCF DataContractSerializer.

Теперь есть SP для исправления этой ошибки: КБ 971030

Вы можете посмотреть на этот аргумент с нескольких точек зрения, в том числе с точки зрения чисто объектно-ориентированного подхода, который говорит, что программировать на основе интерфейса, а не реализации.Учитывая эту мысль, использование IList следует тому же принципу, что и передача и использование интерфейсов, которые вы определяете с нуля.Я также верю в факторы масштабируемости и гибкости, обеспечиваемые интерфейсом в целом.Если класс, реализующий IList<T>, необходимо расширить или изменить, потребляющий код не нужно менять;он знает, чего придерживается контракт интерфейса IList.Однако использование конкретной реализации и List<T> в изменяющемся классе может привести к необходимости изменения и вызывающего кода.Это связано с тем, что класс, придерживающийся IList<T>, гарантирует определенное поведение, которое не гарантируется конкретным типом, использующим List<T>.

Также возможность сделать что-то вроде изменения реализации List<T> по умолчанию в классе. Реализация IList<T>, например, .Add, .Remove или любого другого метода IList, дает разработчику много гибкости и мощности, в противном случае предопределенные List<T>

Как правило, хорошим подходом является использование IList в общедоступном API (при необходимости и семантике списка), а затем внутренний List для реализации API.Это позволяет вам перейти на другую реализацию IList, не нарушая код, использующий ваш класс.

Список имен классов может быть изменен в следующей платформе .net, но интерфейс никогда не изменится, поскольку интерфейс является контрактным.

Обратите внимание: если ваш API будет использоваться только в циклах foreach и т. д., возможно, вместо этого вы захотите просто раскрыть IEnumerable.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top