Через какой интерфейс я должен выставлять List‹T›?

В ответ на этот вопрос runefs предположил, что "если у вас нет особой причины для использования IList, вам следует рассмотреть IEnumerable". Какой вы используете и почему?


person ng5000    schedule 28.05.2009    source источник


Ответы (6)


IEnumberable<T> доступен только для чтения, вам необходимо восстановить коллекцию, чтобы внести в нее изменения. С другой стороны, IList<T> доступен для чтения и записи. Поэтому, если вы ожидаете много изменений в коллекции, откройте IList<T>, но если можно с уверенностью предположить, что вы не будете ее изменять, используйте IEnumerable<T>.

person Tamas Czinege    schedule 28.05.2009
comment
Я согласен. Однако я хотел бы добавить, что если вы просто вернете или приведете свой экземпляр List‹T› к IEnumerable‹T›, получатель всегда сможет вернуть его обратно к IList‹T› или List‹T›. Правильный способ вернуть действительно неизменяемый IEnumerable‹T› — использовать метод AsReadOnly(), который возвращает объект-оболочку, реализующий IEnumerable‹T›, и не может быть приведен обратно к списку. - person LBushkin; 28.05.2009
comment
Кажется, я здесь в меньшинстве, но я до сих пор не понимаю, почему вы поддерживаете IEnumerable. Как говорит Л. Бушкин, это неправильный способ предоставления коллекции, доступной только для чтения, поэтому, если внутри у вас есть объект, реализующий IList, и вы хотите его предоставить, зачем ограничивать клиентский код только использованием подмножества методов IEnumerable? Откуда вы знаете, что клиентский код захочет с ним сделать? Может быть, я думаю об этом с точки зрения написания фреймворка, где клиент абстрактен, а вы потребляете объект в своем собственном коде? - person Martin Harris; 28.05.2009
comment
@Martin Harris: IEnumerable‹T› — де-факто стандартный интерфейс контейнера коллекций в .NET 3.5. Я не уверен, было ли это хорошим решением или нет, но в настоящее время это обычная практика. - person Tamas Czinege; 28.05.2009
comment
Если вам нужна неизменность, я бы предпочел IEnumerable‹T› с использованием yield — таким образом, нет опасности вернуться к IList‹T›. Это также может сократить копирование коллекции. Что касается того, почему более тонкий интерфейс должен быть выставлен, краткий ответ заключается в том, что он уменьшает связь между клиентом и вашей внутренней реализацией (если вы в любом случае не преобразуете внутреннюю коллекцию во внешнее представление). Я также пытаюсь передать операции, ориентированные на коллекцию, в держатель коллекции, а не заставлять клиентов делать это самостоятельно. - person Jim Arnold; 28.05.2009
comment
Хорошо, рискуя спорить (и учитывая текущую ситуацию с голосованием, я думаю, это было бы плохим ходом), не могли бы вы привести мне несколько убедительных примеров из среды .NET 3.5, где внутреннее представление свойства полностью реализует IList, и все же это выставляется как IEnumerable. Быстрое сканирование через отражатель не приводит к каким-либо прыжкам, IEnumerable, похоже, используется в основном там, где он уступает в методе, поэтому IList не будет вариантом. Если я увижу, где это было сделано в реальном мире, возможно, я пойму причину этого. - person Martin Harris; 28.05.2009
comment
Практически все запросы LINQ возвращают коллекции IEnumerable‹T›. Не только LINQ to SQL, но и любой запрос LINQ. Это делает его универсальным наименьшим общим знаменателем. - person Tamas Czinege; 28.05.2009
comment
Но разве это не потому, что они на самом деле не являются списками внутри? Я ищу ситуацию, когда Microsoft могла вернуть IList‹что угодно› или List‹что угодно› и решила вернуть IEnumerable‹что угодно›, чтобы уменьшить связанность. Если это стандарт де-факто для этого, я ожидаю, что дочерние элементы управления в Winforms будут возвращены как IEnumerable и т.д. - person Martin Harris; 28.05.2009
comment
Windows Forms и большинство .NET Frameworks предшествовали LINQ и даже дженерикам. Доктрина IEnumerable‹T› была представлена ​​в версии 3.5 вместе с LINQ. - person Tamas Czinege; 28.05.2009
comment
Итак, укажите мне на объект, который имеет MemoryStream в качестве поля и предоставляет его как свойство IStream, или Array в качестве поля и предоставляет его как IEnumerable. На самом деле мне было бы интересно увидеть какой-нибудь код во фреймворке, где свойство выставляет поле как более базовый тип, чем оно есть на самом деле. - person Martin Harris; 28.05.2009
comment
Вот пример: базовый поток HttpWebRequest и HttpWebResponse предоставляется как поток (даже внутри), хотя на практике он всегда является экземпляром внутреннего класса ConnectionStream. Для общедоступных методов/свойств, предоставляющих интерфейсы IEnumerable‹T›, вам нужно искать классы, представленные в 3.5, одним из примеров является свойство System.Diagnostics.Eventing.Reader.EventLogConfiguration.ProviderNames. - person Tamas Czinege; 28.05.2009
comment
Кого волнует, сделает Microsoft это или нет? У них вряд ли есть послужной список предоставления отличных рекомендаций по проектированию программного обеспечения :-) Отсутствие интерфейсов и абстракций в базовых библиотеках .Net является постоянным источником раздражения, а не рецептом для слепого следования. - person Jim Arnold; 28.05.2009
comment
@DrJokepu - Спасибо за ссылки, я посмотрю и посмотрю, смогу ли я узнать что-нибудь о том, почему они сделали это именно так. @ Джим Арнольд - Как это может раздражать в возвращаемых типах? Когда вы когда-нибудь говорили: «Боже, я бы хотел, чтобы Microsoft вернула здесь IEnumerable, а не гораздо более многофункциональный конкретный класс, я понимаю, как было бы неприятно, если бы они не приняли их, но как возвращаемое значение может быть недостаточно абстрактным? - person Martin Harris; 28.05.2009
comment
Эй, посмотри на это! Вы совершенно правы насчет свойства ProviderNames. Внутри это массив строк, и они представляют его как IEnumerable. Если бы мне пришлось угадывать, почему, я бы сказал, что они не хотят, чтобы старые массивы плавали в их блестящем новом коде 3.5, но, честно говоря, я не думал, что вы их найдете, и это прекрасный пример. Спасибо, что просветили меня. - person Martin Harris; 28.05.2009
comment
Также важно иметь в виду, что у Microsoft могут быть другие проблемы при разработке своих библиотек, которые не будут применяться к собственной библиотеке. Если Microsoft предоставляет список как IEnumerable, то время предоставления доступа к методам списка, если кто-то утверждает, что они нужны, составляет около 2 лет для следующего выпуска .NET. В силу своего положения они вполне могут позволить себе ограничивать типы возвращаемых данных своих API так сильно, как это могли бы сделать другие. - person jerryjvl; 29.05.2009
comment
Это именно моя точка зрения, поскольку первоначальный вопрос был не в том, как я должен выставлять IList, когда я точно знаю, какие функции клиентский код собирается использовать из него, и им никогда не потребуется использовать больше функций, чем предоставляет IEnumerable. Я защищаю политику предоставления клиенту максимально возможной функциональности, поскольку вы не знаете, что ему понадобится. Мой подход кажется стандартным для разработки API (с учетом кода Microsoft, Telerik и Infragistics), поэтому я бы сказал, что цитата в вопросе обратная, и, не зная специфики, вы должны использовать IList. - person Martin Harris; 29.05.2009
comment
@Martin - на мой взгляд, проблема не в ограничении возможностей клиента, а исключительно в управлении зависимостями. Менее абстрактные интерфейсы чаще изменяются, чем более абстрактные. Кроме того, дизайн фреймворка API сильно отличается от дизайна внутреннего приложения и имеет другие проблемы. В любом случае, извините @DrJokepu за кражу вашего ответа :-) - person Jim Arnold; 29.05.2009
comment
Мартин Харрис, Джим Арнольд: Мне очень понравилось это обсуждение, спасибо, что поделились своими мыслями. - person Tamas Czinege; 29.05.2009
comment
Что ж, теперь ответ был принят, и, несмотря на мои самые смелые усилия, я все еще сижу с нулевым количеством голосов, так что я думаю, что остальной мир просто не смотрит на это по-моему (за исключением, возможно, Джеффри Рихтера). - Хотел бы я получить эту цитату). Мне понравился разговор, и я прошу прощения за то, что превратил этот сайт QA в дискуссионный форум. Сейчас откланяюсь и тихонько уйду (бурча себе под нос о том, как однажды оценят мою непонятую гениальность :-)). - person Martin Harris; 29.05.2009

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

И желательно использовать строго типизированные общие версии.

person jerryjvl    schedule 28.05.2009

Я следую принципу, который я прочитал некоторое время назад:

«Потребляй самое простое и выявляй самое сложное»

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

(Отредактировано, чтобы добавить - ну, вот я здесь пару недель спустя, и я только что наткнулся на эту цитату в совершенно другом контексте, похоже, она началась как Принцип надежности или закон Постеля -

Будьте консервативны в том, что вы делаете; будьте либеральны в том, что вы принимаете от других.

Первоначальное определение предназначено для сетевого взаимодействия через Интернет, но я уверен, что видел, как оно переназначается для определения контрактов классов в объектно-ориентированном подходе.)

По сути, если вы определяете метод для внешнего потребления, то параметры должны быть самого базового типа, который дает вам требуемую функциональность - в случае вашего примера это может означать использование IEnumerable вместо IList. Это дает клиентскому коду наибольшую гибкость в отношении того, что они могут передать. С другой стороны, если вы предоставляете свойство для внешнего потребления, делайте это с наиболее сложным типом (IList или ICollection вместо IEnumerable), так как это дает клиенту больше возможностей. гибкость в том, как они используют объект.


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

public class Example
{
    private List<int> _internal = new List<int>();

    public /*To be decided*/ External
    {
        get { return _internal; }
    }
}

Итак, прежде всего давайте предположим, что мы выставляем External как IEnumerable. Причины, по которым я видел это в других ответах и ​​​​комментариях, в настоящее время:

  • IEnumerable доступен только для чтения
  • IEnumerable является стандартом де-факто
  • Это уменьшает связь, поэтому вы можете изменить реализацию
  • Вы не раскрываете детали реализации

Хотя IEnumerable предоставляет только функциональность только для чтения, это не делает его доступным только для чтения. Реальный тип, который вы возвращаете, легко определяется путем отражения или просто приостановки отладчика и поиска, поэтому тривиально вернуть его обратно в List‹>, то же самое относится к примеру FileStream в комментариях ниже. Если вы пытаетесь защитить члена, то притворяться, что это что-то другое, — не лучший способ сделать это.

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

Теперь, уменьшая связанность, с которой я согласен - до определенного момента - в основном этот аргумент, кажется, заключается в том, что вы говорите клиенту, что, хотя возвращаемый вами объект является списком, который я хочу, чтобы вы обрабатывали, как IEnumerable просто на случай, если вы решите позже изменить внутренний код. Игнорируя проблемы с этим и тот факт, что реальный тип все равно выставлен, остается вопрос «зачем останавливаться на IEnumerable?» если вы вернете его как тип объекта, у вас будет полная свобода изменить реализацию на что угодно! Это означает, что вы должны были принять решение о том, сколько функциональности требуется клиентскому коду, поэтому либо вы пишете клиентский код, либо решение основано на произвольных показателях.

Наконец, как обсуждалось ранее, вы можете предположить, что детали реализации всегда доступны в .NET, особенно если вы публикуете объект публично.

Итак, после этой длинной обличительной речи остался один вопрос - Зачем выставлять это как Список‹>? А почему бы не? Вы ничего не выиграли, представив его как IEnumerable, так зачем же искусственно ограничивать возможности клиентского кода работать с объектом? Представьте, если бы Microsoft решила, что коллекция Controls элемента управления Winforms должна отображаться как IEnumerable вместо ControlCollection — вы больше не сможете передавать ее методам, требующим IList, работать с элементами в произвольном индексе или проверять, содержит определенный элемент управления, если вы не разыграете его. Microsoft на самом деле ничего бы не выиграла, а только доставила бы вам неудобства.

person Martin Harris    schedule 28.05.2009
comment
И что произойдет, если по какой-то причине вам придется изменить способ работы бэкэнда, чтобы он использовал другую структуру данных? Предположим, что раньше бэкэнд всегда знал количество возвращаемых им элементов перед построением коллекции, поэтому по этому принципу он возвращал массив. А потом в один прекрасный день обстоятельства меняются, и вы должны использовать вектор или связанный список в будущем. Собираетесь ли вы прыгать через обручи, чтобы преобразовать его обратно в массив, или вы предпочитаете рефакторинг клиентского кода, возможно, ломая сторонний код, который зависит от вашего интерфейса? - person Tamas Czinege; 28.05.2009
comment
Ну, вы уже приняли решение определить тип для своих общедоступных свойств, иначе вы бы выступали за представление всех типов как объектов, что дало бы вам максимальную гибкость для изменения реализации бэкэнда позже. После того, как вы решили, что клиент должен иметь возможность делать с вашим кодом (например, вы можете не поддерживать добавление новых элементов, что исключает использование IList), зачем искусственно ограничивать их только в случае возможности того, что вы можете изменить свое решение позже ? - person Martin Harris; 28.05.2009
comment
Вот что я думаю: давайте предположим, что у нас есть два интерфейса (или класса или чего-то еще), I1 и I2, причем I1 ‹ I2, то есть I2 полностью реализует I1. Если раскрытия I1 достаточно для выполнения работы, I2 означает раскрытие функций, которые не обязательно необходимы, но должны поддерживаться или поддерживаться. Это может не быть проблемой с простыми готовыми структурами, такими как коллекции, но я могу легко представить ситуации, когда это может привести к расползанию функций/клиенту, злоупотребляющему API в более сложных случаях. - person Tamas Czinege; 28.05.2009
comment
Это то, чего я не понимаю — и я ценю, что вы идете со мной вперед и назад по этому вопросу — откуда вы знаете, что разоблачения I1 достаточно, чтобы выполнить работу? Какую работу? Как узнать, какую функциональность клиентский код хочет получить от вашего объекта? - person Martin Harris; 28.05.2009
comment
Хорошо, считайте это. Вы создаете класс, который позволяет клиентскому коду записывать в поток. Теперь внутри вы знаете, что этот поток всегда является FileStream, а не NetworkStream. Вы можете предоставить доступ к FileStream, а не только к потоку, но это будет означать, что клиент будет иметь доступ к FileStream.SafeHandle, то есть дескриптору Windows API потока. Теперь вы можете либо подготовить свой код, чтобы ожидать, что клиент будет возиться с файлом напрямую через Windows API, либо просто скрестить пальцы и надеяться, что этого никогда не произойдет. - person Tamas Czinege; 28.05.2009
comment
Я отредактировал свой ответ, чтобы полностью объяснить свою точку зрения. Надеюсь, это проясняет, почему я с вами не согласен, но я наслаждаюсь беседой, поэтому, пожалуйста, не стесняйтесь продолжать ее. - person Martin Harris; 28.05.2009
comment
Голосую за! Один великолепный голос! Кто бы это ни был, дайте мне знать, если вы когда-нибудь будете в Лондоне, и я куплю вам пива. Теперь я чувствую себя (слегка) оправданным. - person Martin Harris; 29.05.2009
comment
Вопрос не в том, что нужно клиентскому коду, а в том, что готов предоставить API. переход от IEnumerable‹T› к List‹T› очень прост в месте вызова (и почти бесплатен, если фактическим объектом является List‹T›), так зачем взимать плату за реализацию, если она ничего не дает вам? - person Rune FS; 08.09.2011

Когда мне нужно только перечислить дочерние элементы, я использую IEnumerable. Если мне нужен Count, я использую ICollection. Однако я стараюсь этого избегать, потому что это раскрывает детали реализации.

person Rytmis    schedule 28.05.2009

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

person Anton Gogolev    schedule 28.05.2009
comment
Эй, я не в себе! Эта цитата Джеффри Рихтера выглядит так, как будто я говорю о ней в своем ответе. Если у вас будет возможность, не могли бы вы выкопать ссылку, так как мне совсем не повезло найти ее в Интернете? - person Martin Harris; 28.05.2009

Если коллекция, доступная только для чтения, предоставляется через свойство, то обычный способ сделать это — представить ее как ReadOnlyCollection (или ее производный класс), обернув все, что у вас есть. Он предоставляет клиенту все возможности IList, и в то же время четко дает понять, что он доступен только для чтения.

person Pavel Minaev    schedule 07.06.2009