Список <T>или IList <T> - PullRequest
       67

Список <T>или IList <T>

472 голосов
/ 30 декабря 2008

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

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

Ответы [ 18 ]

428 голосов
/ 30 декабря 2008

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

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

299 голосов
/ 06 октября 2009

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

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

Software astronauts

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

183 голосов
/ 30 декабря 2008

Интерфейс - это обещание (или контракт).

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

66 голосов
/ 12 апреля 2016

Некоторые люди говорят: «всегда используйте 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 / ICollection неверен. Если вы используете ICollection , вам необходимо проверить флаг 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 Framework. 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 и указывает, могут ли элементы быть добавлены или удалены из коллекции; но просто чтобы действительно запутать вещи, это не указывает, могут ли они быть заменены, что в случае массивов (которые возвращают IsReadOnlys == true) может быть.

Подробнее об IsReadOnly см. msdn определение ICollection .IsReadOnly

37 голосов
/ 30 декабря 2008

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

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

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

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

27 голосов
/ 09 сентября 2009

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

18 голосов
/ 30 декабря 2008

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

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

class A : B, IList<T> { ... }
14 голосов
/ 30 декабря 2008

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

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

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

12 голосов
/ 30 декабря 2008
public void Foo(IList<Bar> list)
{
     // Do Something with the list here.
}

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

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

11 голосов
/ 18 октября 2015

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

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

  • AddRange
  • AsReadOnly
  • BinarySearch
  • Емкость
  • ConvertAll
  • * 1016 Exists *
  • Найти
  • FindAll
  • FindIndex
  • FindLast
  • FindLastIndex
  • ForEach
  • GetRange
  • InsertRange
  • LastIndexOf
  • RemoveAll
  • RemoveRange
  • Реверс
  • Сортировка
  • ToArray
  • TrimExcess
  • TrueForAll
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...