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





Если вы представляете свой класс через библиотеку, которую будут использовать другие, вы обычно хотите раскрыть его через интерфейсы, а не через конкретные реализации. Это поможет, если вы позже решите изменить реализацию своего класса, чтобы использовать другой конкретный класс. В этом случае пользователям вашей библиотеки не нужно будет обновлять свой код, поскольку интерфейс не меняется.
Если вы используете его только для внутренних целей, вам может быть все равно, и использование List<T> может быть нормальным.
Я не понимаю (тонкой) разницы, есть ли какой-нибудь пример, на который вы могли бы указать мне?
Скажем, вы изначально использовали List <T> и хотели изменить использование специализированного CaseInsensitiveList <T>, оба из которых реализуют IList <T>. Если вы используете конкретный тип, необходимо обновить всех вызывающих абонентов. Если отображается как IList <T>, вызывающего абонента изменять не нужно.
Ах. ОК. Я понимаю. Выбор наименьшего общего знаменателя для контракта. Спасибо!
Этот принцип является примером инкапсуляции, одного из трех столпов объектно-ориентированного программирования. Идея состоит в том, что вы скрываете детали реализации от пользователя, а вместо этого предоставляете им стабильный интерфейс. Это сделано для уменьшения зависимости от деталей, которые могут измениться в будущем.
Также обратите внимание на то, что T []: IList <T>, так что по соображениям производительности вы всегда можете переключиться с возврата List <T> из вашей процедуры на возвращение T [], и если процедура объявлена для возврата IList <T> (или, возможно, ICollection <T> или IEnumerable <T>) ничего больше менять не нужно.
API Microsoft .NET 4.0 использует List <T> вместо IList <T> ... Интересно, почему? msdn.microsoft.com/en-us/library/…
По соображениям производительности вы не хотите получать доступ к T[] через его интерфейс IEnumerable<T>, также массивы не поддерживают, например. IList.Remove
Существует довольно странное нарушение LSP в реализации Array в .Net, хотя Array реализует IList <T>, вызывая метод .Add (item) в IList <T>, конкреция которого является массивом, приведет к исключению. Имейте это в виду, если вы решите использовать IList <T> в своих контрактах.
@Gent Array реализует IList, а не IList<T>, поэтому я не уверен, что понимаю, о чем вы говорите.
Извините, что вы правы, это IList, но нарушение LSP все еще применяется.
дополнительную информацию можно найти здесь: stackoverflow.com/questions/11163297/…
@jason «Выбор наименьшего общего знаменателя для контракта». - один из лучших способов объяснить использование интерфейса. Просто вау.
IList <T> - это интерфейс, поэтому вы можете наследовать другой класс и по-прежнему реализовывать IList <T>, в то время как наследование List <T> не позволяет вам это сделать.
Например, если есть класс A, и ваш класс B наследует его, вы не можете использовать List <T>
class A : B, IList<T> { ... }
Вы бы сделали это, потому что определение IList или ICollection откроет для других реализаций ваших интерфейсов.
Вы можете захотеть иметь IOrderRepository, который определяет коллекцию заказов в IList или ICollection. Тогда у вас могут быть разные виды реализаций для предоставления списка заказов, если они соответствуют «правилам», определенным вашим IList или ICollection.
Принцип TDD и ООП обычно заключается в программировании интерфейса, а не реализации.
В этом конкретном случае, поскольку вы по существу говорите о языковой конструкции, а не о пользовательской, это обычно не имеет значения, но скажем, например, что вы обнаружили, что List не поддерживает то, что вам нужно. Если бы вы использовали IList в остальной части приложения, вы могли бы расширить List своим собственным классом и по-прежнему иметь возможность передавать его без рефакторинга.
Стоимость этого минимальна, почему бы не избавить себя от головной боли позже? В этом суть принципа интерфейса.
Если ваш ExtendedList <T> наследует List <T>, вы все равно можете поместить его в контракт List <T>. Этот аргумент работает только в том случае, если вы пишете свою собственную реализацию IList <T> из sratch (или, по крайней мере, без наследования List <T>)
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<T> - это конкретная реализация IList<T>, который представляет собой контейнер, к которому можно обращаться так же, как к линейному массиву T[], используя целочисленный индекс. Когда вы указываете IList<T> в качестве типа аргумента метода, вы только указываете, что вам нужны определенные возможности контейнера.
Например, спецификация интерфейса не требует использования конкретной структуры данных. Реализация List<T> обеспечивает такую же производительность при доступе, удалении и добавлении элементов, что и линейный массив. Однако вы можете представить себе реализацию, которая вместо этого поддерживается связанным списком, для которого добавление элементов в конец обходится дешевле (с постоянным временем), но с произвольным доступом намного дороже. (Обратите внимание, что .NET LinkedList<T> действительно нет реализует IList<T>.)
Этот пример также сообщает вам, что могут быть ситуации, когда вам нужно указать реализацию, а не интерфейс в списке аргументов: в этом примере, когда вам требуется конкретная характеристика производительности доступа. Обычно это гарантируется для конкретной реализации контейнера (документация List<T>: «Он реализует общий интерфейс IList<T> с использованием массива, размер которого динамически увеличивается по мере необходимости»).
Кроме того, вы можете рассмотреть возможность раскрытия минимально необходимой вам функциональности. Например. если вам не нужно изменять содержимое списка, вам, вероятно, следует рассмотреть возможность использования IEnumerable<T>, который расширяет IList<T>.
Что касается вашего последнего утверждения об использовании IEnumerable вместо IList. Это не всегда рекомендуется, возьмите, например, WPF, который просто создаст объект оболочки IList, поэтому будет влиять на perf - msdn.microsoft.com/en-us/library/bb613546.aspx
Отличный ответ, гарантии производительности - это то, чего не может предоставить интерфейс. В некотором коде это может быть очень важно, и использование конкретных классов сообщает о ваших намерениях, о вашей потребности в этом конкретном классе. С другой стороны, интерфейс говорит: «Мне просто нужно вызвать этот набор методов, никаких других контрактов не предполагается».
@joshperry Если производительность интерфейса вас беспокоит, вы в первую очередь выбрали неправильную платформу. Impo, это микрооптимизация.
@nawfal Я не комментировал плюсы и минусы производительности интерфейсов. Я соглашался и комментировал тот факт, что, когда вы решаете предоставить конкретный класс в качестве аргумента, вы можете сообщить о намерении производительности. Сравнение LinkedList<T> и List<T> и IList<T> дает некоторые гарантии производительности, необходимые для вызываемого кода.
Интерфейс - это обещание (или контракт).
Как всегда с обещаниями - меньше тем лучше.
Не всегда лучше, просто сохранить проще! :)
Я этого не понимаю. Итак, IList - это обещание. Какое здесь обещание меньше и лучше? Это не логично.
Для любого другого класса я бы согласился. Но не для List<T>, потому что AddRange не определен в интерфейсе.
В данном конкретном случае это не очень полезно. Лучшее обещание - это то, что сдерживается. IList<T> дает обещания, которые он не может выполнить. Например, есть метод Add, который может выдать, если IList<T> доступен только для чтения. List<T>, с другой стороны, всегда доступен для записи и, таким образом, всегда выполняет свои обещания. Кроме того, начиная с .NET 4.6, у нас теперь есть IReadOnlyCollection<T> и IReadOnlyList<T>, которые почти всегда подходят лучше, чем IList<T>. И они ковариантны. Избегайте IList<T>!
@ZunTzu Я бы сказал, что кто-то, передающий неизменяемую коллекцию как изменяемую, сознательно нарушил представленный контракт - это не проблема самого контракта, например IList<T>. Кто-то мог бы с таким же успехом реализовать IReadOnlyList<T> и заставить каждый метод генерировать исключение. Это своего рода противоположность утиной печати - вы называете это уткой, но она не может так крякать. Однако это часть контракта, даже если компилятор не может ее обеспечить. Я на 100% согласен с тем, что IReadOnlyList<T> или IReadOnlyCollection<T> следует использовать для доступа только для чтения.
@Shelby Oldfield Я согласен с тем, что передача неизменяемой коллекции в качестве IList является очевидной ошибкой, начиная с .NET 4.6 (и, скорее всего, компилятор обнаружит ее). Но могут быть и более коварные случаи, например передача массива C# в виде списка IList. Я не уверен, что все знают, что массивы реализуют IList, а это означает, что нельзя предполагать поддержку Add.
@ZunTzu Я бы сказал, что это все еще на вызывающем / любом, кто решил передать массив для IList, но я полностью согласен с тем, что нелепо, что Array `` реализует '' IList в первую очередь ... вы можете технически проверить IList. IsFixedSize (о котором я не знал, было что-то еще минуту назад. Кроме того, его нет в IList <T> по какой-то причине?), Так что, возможно, он не нарушает контракт IList, но ... это все еще своего рода bs . Я действительно понятия не имею, почему нет IReadOnlyList, IFixedSizeList и IList, каждый из которых реализует предыдущий ... Я немного сбит с толку тем, что он работает именно так.
Я бы немного перевернул вопрос, вместо того, чтобы объяснять, почему вы должны использовать интерфейс вместо конкретной реализации, постарайтесь обосновать, почему вы должны использовать конкретную реализацию, а не интерфейс. Если вы не можете это оправдать, воспользуйтесь интерфейсом.
Очень интересный способ думать об этом. И хороший способ думать при программировании - спасибо.
Хорошо сказано! Поскольку интерфейс является более гибким подходом, вам действительно нужно будет обосновать, что именно вам дает конкретная реализация.
Отличный образ мышления. Тем не менее я могу ответить на него: моя причина называется AddRange ()
моя причина называется Add (). иногда вам нужен список, который вы знать может принимать дополнительные элементы, не так ли? Смотрите мой ответ.
Менее популярный ответ - программисты любят притворяться, что их программное обеспечение будет повторно использоваться во всем мире, когда на самом деле большинство проектов будет поддерживаться небольшим количеством людей, и какими бы хорошими ни были звуковые фрагменты, связанные с интерфейсом, вы вводите в заблуждение. сам.
Архитектура Астронавты. Вероятность того, что вы когда-нибудь напишете свой собственный список IList, который добавит что-либо к уже имеющимся в .NET framework, настолько мала, что это теоретическая желеобразная масса, зарезервированная для «лучших практик».

Очевидно, если вас спросят, что вы используете в интервью, вы скажете «Ilyist», улыбнетесь, и оба будут довольны своим умом. Или для общедоступного API, IList. Надеюсь, вы поняли мою точку зрения.
Я не согласен с твоим язвительным ответом. Я полностью не согласен с тем, что чрезмерная архитектура является реальной проблемой. Однако я думаю, что особенно в случае коллекций интерфейсы действительно сияют. Скажем, у меня есть функция, которая возвращает IEnumerable<string>, внутри функции я могу использовать List<string> для внутреннего резервного хранилища для создания моей коллекции, но я хочу, чтобы вызывающие абоненты только перечисляли его содержимое, а не добавляли или удаляли. Принятие интерфейса в качестве параметра передает аналогичное сообщение: «Мне нужен набор строк, не беспокойтесь, я не буду его менять».
Разработка программного обеспечения - это воплощение намерения. Если вы думаете, что интерфейсы полезны только для создания крупногабаритных грандиозных архитектур и им не место в небольших магазинах, то я надеюсь, что человек, сидящий напротив вас на собеседовании, не я.
Использование IList вместо List даже не вводит никаких дополнительных слов в код, поэтому я с трудом верю аргументу, что это приводит к ненужному коду.
@Arec Barrwin: Вы выбираете раскрытие List, а затем многие клиенты используют его и зависят от него. Они решают вызвать в списке методы, которых нет в IList. Позже вы обнаружите, что использование List вызывает проблемы с вашей алгоритмической сложностью, и вам нужно изменить реализацию List. Получайте удовольствие от этого.
@joshperry - это не обсуждение IList vs List, а не IEnumerable (хотя вы хорошо замечаете)? Я думаю, это о лучших практиках: вы следуете им, однако в действительности для многих людей они пишут внутреннее программное обеспечение для бизнеса, используемое их командой. Очевидно, что для распределенных API List - плохой выбор.
Кто-то должен был сказать этого Арека ... даже если в результате у вас есть всевозможные академические паттерны, гонящиеся за письмами ненависти. +1 для всех нас, кто ненавидит, когда небольшое приложение загружается с интерфейсами и нажатие на «найти определение» приводит нас куда-нибудь, а не к источнику проблемы ... Могу я позаимствовать фразу «Архитектурные астронавты»? Я вижу, это пригодится.
Если можно, я дам вам за этот ответ 1000 баллов. : D
Не уверен, что этот ответ имеет отношение к вопросу, в котором никогда не упоминалось о написании вашей собственной реализации. И IList, и List находятся в Framework; использование List ограничивает вас только одним; использование IList позволяет переключаться между реализациями Framework (даже если вы никогда не пишете свои собственные). @Gats, щелкнув «Найти определение» как для List, так и для IList, приведет вас к Framework, поэтому я не понимаю, насколько это актуально.
Я говорил о более широком результате поиска паттернов. Интерфейсы для общих интерфейсов хороши, лишние слои ради погони за шаблоном - плохо.
Я думаю, вы путаете чрезмерную архитектуру с хорошими практиками. Я использую интерфейсы, потому что это позволяет тестировать мой код. Важно иметь возможность подделать / имитировать возврат, чтобы проверить логику. Это также зависит от того, что вы строите. Если я создаю небольшие консольные приложения для утилит, я выберу List <T>. Даже если мой код можно повторно использовать только в рамках моей организации (например, никогда не выходить в общедоступную сеть), я все равно использую интерфейсы ... для тестирования.
@ DJSPIN80 Интерфейсы волшебным образом не делают ваш код более тестируемым. Я знаю, что то, что я только что сказал, является ересью, но вы не получаете ничего бесплатно только потому, что это интерфейс.
@Gats отныне у нас есть «Перейти к реализации» для этой проблемы. :)
Я хихикнул. Спасибо за юмор, который мы все хотим когда-нибудь стать космонавтами! Поехали на Марс !!!
«Шансы, что вы когда-нибудь напишете свой собственный список IList, который добавит что-нибудь к уже имеющимся в .NET framework, настолько малы ...» Удаленно? Вы делаете это каждый день, когда создаете IList<int> temp = new int[5]. Если вы жестко кодируете List<T> в общедоступном API, вы должны выполнить неприятное преобразование (с O (n)) из int[] в List<T>, чтобы передать массив!
Хотя было сложно распечатать свой пост на одной странице - он сейчас висит у меня в кабинете на стене (в полном цвете). LOL Эрик
Настоящая проблема в том, что IList<T> не приближается к удобству использования List<T>. В IList<T> так много не хватает интерфейса, что иногда трудно сказать, какое отношение к нему имеет List<T>. Другими словами, интерфейс IList<T> настолько минималистичен, что практически бесполезен. Возможно, Microsoft следовало назвать его IIncompleteList<T>.
Интерфейс гарантирует, что вы, по крайней мере, получите ожидаемые методы.; осведомленность об определении интерфейса, т.е. все абстрактные методы, которые должны быть реализованы любым классом, наследующим интерфейс. поэтому, если кто-то создает собственный огромный класс с несколькими методами, помимо тех, которые он унаследовал от интерфейса для некоторых дополнительных функций, и они бесполезны для вас, лучше использовать ссылку на подкласс (в этом случае interface) и назначьте ему конкретный объект класса.
Дополнительным преимуществом является то, что ваш код защищен от любых изменений в конкретном классе, поскольку вы подписываетесь только на несколько методов конкретного класса, и это те, которые будут там, пока конкретный класс наследует от интерфейса, которым вы являетесь. с использованием. так что это безопасность для вас и свобода для кодировщика, который пишет конкретную реализацию, для изменения или добавления дополнительных функций в свой конкретный класс.
Вы можете взглянуть на этот аргумент с нескольких точек зрения, включая чисто объектно-ориентированный подход, который гласит, что нужно программировать интерфейс, а не реализацию. С этой мыслью использование IList следует тому же принципу, что и передача и использование интерфейсов, которые вы определяете с нуля. Я также верю в факторы масштабируемости и гибкости, обеспечиваемые интерфейсом в целом. Если класс, реализующий IList <T>, должен быть расширен или изменен, потребляющий код не должен изменяться; он знает, чего придерживается контракт интерфейса IList. Однако использование конкретной реализации и List <T> в классе, который изменяется, может привести к необходимости изменения вызывающего кода. Это связано с тем, что класс, придерживающийся IList <T>, гарантирует определенное поведение, которое не гарантируется конкретным типом, использующим List <T>.
Кроме того, возможность делать что-то вроде изменения реализации List <T> по умолчанию в классе, реализующем IList <T>, например, .Add, .Remove или любой другой метод IList, дает разработчику много гибкости и мощности, иначе предопределенного. по List <T>
IList <> почти всегда предпочтительнее в соответствии с советом другого автора, однако обратите внимание на есть ошибка в .NET 3.5 sp 1 при запуске IList <> через более чем один цикл сериализации / десериализации с помощью WCF DataContractSerializer.
Теперь есть SP для исправления этой ошибки: КБ 971030
Что, если .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
List<T> реализует эти девять методов (не включая методы расширения), кроме того, у него есть около 41 общедоступного метода, что влияет на ваше решение, какой из них использовать в вашем приложении.
List<T>Ссылка MSDN
Предполагая, что ни один из этих вопросов (или ответов) List vs IList не упоминает различие подписи. (Вот почему я искал этот вопрос на SO!)
Итак, вот методы, содержащиеся в List, которых нет в IList, по крайней мере, с .NET 4.5 (около 2015 года).
Не совсем так. Reverse и ToArray - это методы расширения для интерфейса IEnumerable <T>, от которого происходит IList <T>.
Это потому, что они имеют смысл только в List, а не в других реализациях 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 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 <T> и указывает, могут ли элементы быть добавлены или удалены из коллекции; но, чтобы действительно запутать вещи, он не указывает, можно ли их заменить, что может быть в случае массивов (которые возвращают IsReadOnlys == true).
Дополнительные сведения об IsReadOnly см. В разделе msdn определение ICollection <T> .IsReadOnly.
Отличный, отличный ответ. Я хотел бы добавить, что, даже если IsReadOnly является true для IList, вы все равно можете изменять его текущие члены! Может быть, это свойство следует назвать IsFixedSize?
(это было протестировано с передачей Array как IList, поэтому я мог изменить текущие члены)
@SamPearson было свойство IsFixedSize в IList в .NET 1, не включенное в общий IList <T> .NET 2.0, где оно было объединено с IsReadOnly, что привело к неожиданному поведению вокруг массивов. Здесь есть подробный блог о тонких различиях: enterprisecraftsmanship.com/2014/11/22/…
Отличный ответ! Кроме того, начиная с .NET 4.6, у нас теперь есть IReadOnlyCollection<T> и IReadOnlyList<T>, которые почти всегда лучше подходят, чем IList<T>, но не имеют опасной ленивой семантики IEnumerable<T>. И они ковариантны. Избегайте IList<T>!
Как правило, хорошим подходом является использование IList в общедоступном API (когда это необходимо и требуется семантика списка), а затем внутреннее использование List для реализации API. Это позволяет вам перейти на другую реализацию IList, не нарушая код, использующий ваш класс.
Список имен классов может быть изменен в следующей .net framework, но интерфейс никогда не изменится, поскольку интерфейс является контрактом.
Обратите внимание, что если ваш API будет использоваться только в циклах foreach и т. д., Тогда вы можете вместо этого рассмотреть возможность использования IEnumerable.
поищите в Интернете по запросу «код интерфейса, а не реализация», и вы можете читать весь день .. и найдете несколько интересных войн.