Может кто-нибудь объяснить мне, почему я хотел бы использовать IList над списком в C #?
Смежный вопрос: почему считается плохим разоблачатьList<T>
Может кто-нибудь объяснить мне, почему я хотел бы использовать IList над списком в C #?
Смежный вопрос: почему считается плохим разоблачатьList<T>
Ответы:
Если вы представляете свой класс через библиотеку, которую будут использовать другие, вы, как правило, хотите представить его через интерфейсы, а не через конкретные реализации. Это поможет, если вы решите изменить реализацию вашего класса позже, чтобы использовать другой конкретный класс. В этом случае пользователям вашей библиотеки не нужно будет обновлять свой код, поскольку интерфейс не меняется.
Если вы просто используете его для внутреннего использования, вам может быть все равно, и использование
List<T>
может быть нормальным.источник
Менее популярный ответ - программисты любят притворяться, что их программное обеспечение будет повторно использоваться во всем мире, когда большинство проектов будут обслуживаться небольшим количеством людей, и как бы ни были хороши звуковые фрагменты, связанные с интерфейсом, вы обманываете сами.
Архитектура космонавтов . Скорее всего, вы когда-нибудь напишете свой собственный IList, который добавит что-либо к тем, что уже есть в .NET Framework, настолько мал, что это теоретические желе, зарезервированные для «лучших практик».
Очевидно, что если вас спрашивают, что вы используете в интервью, вы говорите IList, улыбнитесь, и оба выглядят довольными собой за то, что они такие умные. Или для публичного API, IList. Надеюсь, вы поняли мою точку зрения.
источник
IEnumerable<string>
внутри функции, которую я могу использоватьList<string>
для внутреннего резервного хранилища, чтобы сгенерировать мою коллекцию, но я только хочу, чтобы вызывающие абоненты перечисляли ее содержимое, а не добавляли или удаляли. Принятие интерфейса в качестве параметра приводит к аналогичному сообщению «Мне нужна коллекция строк, но не беспокойтесь, я не буду ее менять».Интерфейс - это обещание (или контракт).
Как всегда с обещаниями - чем меньше, тем лучше .
источник
IList
и обещание. Что здесь меньше и лучше? Это не логично.List<T>
, потому чтоAddRange
не определяется на интерфейсе.IList<T>
дает обещания, которые он не может сдержать. Например, естьAdd
метод, который может выдать, если он доступен толькоIList<T>
для чтения.List<T>
с другой стороны, всегда доступен для записи и, следовательно, всегда выполняет свои обещания. Кроме того, начиная с .NET 4.6, у нас теперь естьIReadOnlyCollection<T>
иIReadOnlyList<T>
которые почти всегда лучше, чемIList<T>
. И они ковариантны. ИзбегайтеIList<T>
!IList<T>
. Кто-то может также реализоватьIReadOnlyList<T>
и заставить каждый метод также выдавать исключение. Это как бы противоположность набиранию уток - вы называете это уткой, но она не может крякать как одна. Это часть контракта, даже если компилятор не может обеспечить его выполнение. Я на 100% согласен, чтоIReadOnlyList<T>
илиIReadOnlyCollection<T>
должен использоваться для доступа только для чтения, хотя.Некоторые люди говорят «всегда используйте
IList<T>
вместоList<T>
».Они хотят, чтобы вы изменили свои сигнатуры метода с
void Foo(List<T> input)
наvoid Foo(IList<T> input)
.Эти люди не правы.
Это более нюанс, чем это. Если вы возвращаете
IList<T>
как часть общедоступного интерфейса в свою библиотеку, вы оставляете себе интересные варианты, чтобы, возможно, создать собственный список в будущем. Возможно, вам никогда не понадобится такая опция, но это аргумент. Я думаю, что это весь аргумент для возврата интерфейса вместо конкретного типа. Стоит упомянуть, но в этом случае у него есть серьезный недостаток.Как незначительный контраргумент, вы можете обнаружить, что каждый звонящий нуждается в
List<T>
любом случае, и код вызова усеян.ToList()
Но гораздо более важно, если вы принимаете в IList в качестве параметра вы бы лучше быть осторожным, потому что
IList<T>
иList<T>
не ведут себя точно так же. Несмотря на сходство в названии, и несмотря на то, что они делятся интерфейсом, они не раскрывают один и тот же контракт .Предположим, у вас есть этот метод:
Полезный коллега «рефакторинг» метод, чтобы принять
IList<int>
.Ваш код теперь не работает, потому что
int[]
реализуетIList<int>
, но имеет фиксированный размер. Контракт дляICollection<T>
(базаIList<T>
) требует кода, который использует его для проверкиIsReadOnly
флага, прежде чем пытаться добавить или удалить элементы из коллекции. Контракт наList<T>
не имеет.Принцип подстановки Лискова (упрощенный) гласит, что производный тип должен использоваться вместо базового типа без каких-либо дополнительных предварительных условий или постусловий.
Такое ощущение, что это нарушает принцип подстановки Лискова.
Но это не так. Ответ на этот вопрос заключается в том, что в примере использовался IList <T> / ICollection <T> неправильно. Если вы используете ICollection <T>, вам нужно проверить флаг IsReadOnly.
Если кто-то передает вам массив или список, ваш код будет работать нормально, если вы каждый раз проверяете флаг и у вас есть запасной вариант ... Но на самом деле; кто так делает? Не знаете заранее, нужен ли вашему методу список, который может принимать дополнительных членов; Вы не указываете это в сигнатуре метода? Что именно вы собираетесь делать, если вам передают список только для чтения, как
int[]
?Вы можете подставить
List<T>
в код, который используетIList<T>
/ICollection<T>
правильно . Вы не можете гарантировать, что сможете заменитьIList<T>
/ICollection<T>
в код, который используетList<T>
.Существует много призывов к принципу единой ответственности / принципу разделения интерфейсов во многих аргументах, чтобы использовать абстракции вместо конкретных типов - в зависимости от самого узкого интерфейса. В большинстве случаев, если вы используете a
List<T>
и думаете, что вместо этого можете использовать более узкий интерфейс - почему бы и нетIEnumerable<T>
? Это часто лучше подходит, если вам не нужно добавлять элементы. Если вам нужно добавить к коллекции, используйте тип бетона,List<T>
.Для меня
IList<T>
(иICollection<T>
) это худшая часть .NET Framework.IsReadOnly
нарушает принцип наименьшего удивления. Класс, такой какArray
, который никогда не позволяет добавлять, вставлять или удалять элементы, не должен реализовывать интерфейс с методами Add, Insert и Remove. (см. также /software/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, см. Определение msoln ICollection <T> .IsReadOnly
источник
IsReadOnly
этоtrue
дляIList
, вы все еще можете изменить его текущие члены! Может быть, это свойство должно быть названоIsFixedSize
?Array
как aIList
, поэтому я мог изменить текущих членов)IReadOnlyCollection<T>
иIReadOnlyList<T>
которые почти всегда лучше подходят, чем,IList<T>
но не имеют опасной ленивой семантикиIEnumerable<T>
. И они ковариантны. ИзбегайтеIList<T>
!List<T>
это конкретная реализацияIList<T>
, которая является контейнером, который может быть адресован так же, как линейный массивT[]
с использованием целочисленного индекса. Когда вы указываетеIList<T>
в качестве типа аргумента метода, вы указываете только то, что вам нужны определенные возможности контейнера.Например, спецификация интерфейса не предписывает использование определенной структуры данных. Реализация
List<T>
происходит с той же производительностью для доступа, удаления и добавления элементов как линейный массив. Однако вы могли бы представить реализацию, которая вместо этого поддерживается связанным списком, для которого добавление элементов в конец дешевле (постоянное время), но произвольный доступ намного дороже. (Обратите внимание , что .NETLinkedList<T>
никак не реализоватьIList<T>
.)В этом примере также говорится, что могут быть ситуации, когда вам нужно указать реализацию, а не интерфейс, в списке аргументов: в этом примере, когда вам требуется конкретная характеристика производительности доступа. Обычно это гарантируется для конкретной реализации контейнера (
List<T>
документация: «Он реализуетIList<T>
общий интерфейс, используя массив, размер которого динамически увеличивается по мере необходимости».).Кроме того, вы можете рассмотреть возможность предоставления минимальной функциональности, которая вам нужна. Например. если вам не нужно изменять содержимое списка, вы, вероятно, должны рассмотреть возможность использования
IEnumerable<T>
, котороеIList<T>
расширяет.источник
LinkedList<T>
против взятияList<T>
противIList<T>
всех сообщает что-то о гарантиях производительности, требуемых вызываемым кодом.Я бы немного перевернул вопрос, вместо того, чтобы обосновать, почему вы должны использовать интерфейс поверх конкретной реализации, попытаться обосновать, почему вы будете использовать конкретную реализацию, а не интерфейс. Если вы не можете оправдать это, используйте интерфейс.
источник
IList <T> является интерфейсом, поэтому вы можете наследовать другой класс и при этом реализовать IList <T>, тогда как наследование List <T> не позволяет вам сделать это.
Например, если есть класс A и ваш класс B наследует его, вы не можете использовать List <T>
источник
Принцип TDD и ООП обычно заключается в программировании интерфейса, а не реализации.
В этом конкретном случае, поскольку вы по сути говорите о языковой конструкции, а не о пользовательской конструкции, это обычно не имеет значения, но, например, скажем, что вы обнаружили, что List не поддерживает то, что вам нужно. Если бы вы использовали IList в остальной части приложения, вы могли бы расширить List своим собственным классом и по-прежнему иметь возможность передавать его без рефакторинга.
Затраты на это минимальны, почему бы не избавить себя от головной боли позже? В этом и заключается принцип интерфейса.
источник
В этом случае вы можете передать любой класс, который реализует интерфейс IList <Bar>. Если бы вы использовали взамен List <Bar>, можно было передать только экземпляр List <Bar>.
Способ IList <Bar> более слабо связан, чем способ List <Bar>.
источник
Подчеркивая, что ни в одном из этих вопросов (или ответов) относительно Списка или IList не упоминается разница подписей. (Вот почему я искал этот вопрос на SO!)
Итак, вот методы, содержащиеся в List, которых нет в IList, по крайней мере, начиная с .NET 4.5 (около 2015 г.)
источник
Reverse
иToArray
являются методами расширения для интерфейса IEnumerable <T>, из которого происходит IList <T>.List
s, а не в других реализацияхIList
.Наиболее важный случай использования интерфейсов над реализациями - это параметры вашего API. Если ваш API принимает параметр List, то любой, кто его использует, должен использовать List. Если тип параметра - IList, то вызывающая сторона обладает гораздо большей свободой и может использовать классы, о которых вы никогда не слышали, которых, возможно, даже не существовало на момент написания вашего кода.
источник
Что делать, если .NET 5.0 заменяет
System.Collections.Generic.List<T>
наSystem.Collection.Generics.LinearList<T>
. .NET всегда владеет именем,List<T>
но они гарантируют, чтоIList<T>
это контракт. Таким образом, ИМХО, мы (по крайней мере, я) не должны использовать чье-то имя (хотя в данном случае это .NET) и попадаем в неприятности позже.В случае использования
IList<T>
вызывающая сторона всегда гарантирует работу вещей, и разработчик может изменить базовую коллекцию на любую альтернативную конкретную реализациюIList
источник
Все концепции в основном изложены в большинстве ответов выше относительно того, зачем использовать интерфейс поверх конкретных реализаций.
IList<T>
Ссылка MSDNList<T>
реализует эти девять методов (не включая методы расширения), кроме того, он имеет около 41 общедоступных методов, что зависит от того, какой из них использовать в вашем приложении.List<T>
Ссылка MSDNисточник
Вы бы, потому что определение IList или ICollection открыло бы для других реализаций ваших интерфейсов.
Возможно, вы захотите иметь IOrderRepository, который определяет коллекцию заказов в IList или ICollection. Тогда у вас могут быть различные виды реализаций для предоставления списка заказов, если они соответствуют «правилам», определенным вашим IList или ICollection.
источник
IList <> почти всегда предпочтителен согласно совету другого автора, однако обратите внимание, что в .NET 3.5 sp 1 есть ошибка при запуске IList <> через более одного цикла сериализации / десериализации с WCF DataContractSerializer.
Теперь есть SP, чтобы исправить эту ошибку: KB 971030
источник
Интерфейс гарантирует, что вы по крайней мере получите методы, которые вы ожидаете ; быть в курсе определения интерфейса т.е. все абстрактные методы, которые должны быть реализованы любым классом, наследующим интерфейс. поэтому, если кто-то создает собственный огромный класс с несколькими методами, помимо тех, которые он унаследовал от интерфейса для некоторой дополнительной функциональности, и которые вам бесполезны, лучше использовать ссылку на подкласс (в этом случае интерфейс) и назначить ему конкретный объект класса.
дополнительное преимущество заключается в том, что ваш код защищен от любых изменений в конкретном классе, так как вы подписываетесь только на несколько методов конкретного класса, и это те, которые будут там, пока конкретный класс наследует от интерфейса, которым вы являетесь с помощью. так что это безопасность для вас и свобода для кодировщика, который пишет конкретную реализацию, чтобы изменить или добавить больше функциональности в его конкретный класс.
источник
Вы можете взглянуть на этот аргумент с нескольких точек зрения, в том числе с точки зрения чисто ОО-подхода, который говорит, что программирование на основе интерфейса не является реализацией. С этой мыслью использование IList следует тому же принципу, что и обход и использование интерфейсов, которые вы определяете с нуля. Я также верю в факторы масштабируемости и гибкости, предоставляемые интерфейсом в целом. Если класс, реализующий IList <T>, необходимо расширить или изменить, код потребления не должен изменяться; он знает, чего придерживается контракт IList Interface. Однако использование конкретной реализации и List <T> для класса, который изменяется, также может привести к необходимости изменения вызывающего кода. Это потому, что класс, придерживающийся IList <T>, гарантирует определенное поведение, которое не гарантируется конкретным типом, использующим List <T>.
Кроме того, имея возможность делать что-то вроде изменения стандартной реализации List <T> в классе реализующий IList <T>, например .Add, .Remove или любой другой метод IList, дает разработчику большую гибкость и мощь, в противном случае предопределенные по списку <T>
источник
Как правило, хорошим подходом является использование IList в общедоступном API-интерфейсе (когда это необходимо, и семантика списка необходима), а затем внутренний список для реализации API. Это позволяет вам перейти на другую реализацию IList, не нарушая код, который использует ваш класс.
Имя класса List может быть изменено в следующей среде .net, но интерфейс никогда не изменится, так как интерфейс является контрактным.
Обратите внимание, что если ваш API будет использоваться только в циклах foreach и т. Д., Вы можете вместо этого рассмотреть возможность предоставления IEnumerable вместо этого.
источник