Должен ли я всегда возвращать IEnumerable ‹T› вместо IList ‹T›?

Когда я пишу свой DAL или другой код, который возвращает набор элементов, должен ли я всегда делать оператор return:

public IEnumerable<FooBar> GetRecentItems()

or

public IList<FooBar> GetRecentItems()

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


person KingNestor    schedule 02.07.2009    source источник
comment
Это List ‹T› или IList ‹T›, о котором вы спрашиваете? Название и вопрос говорят о разном ...   -  person Fredrik Mörk    schedule 02.07.2009
comment
Когда это возможно, пользовательский интерфейс IEnumerable или Ilist instaead конкретного типа.   -  person Usman Masood    schedule 02.07.2009
comment
Я возвращаю коллекции в виде списка ‹T›. Я не вижу необходимости возвращать IEnumberable ‹T›, так как вы можете извлечь его из списка ‹T›   -  person Chuck Conway    schedule 03.07.2009
comment
см. также: stackoverflow.com/questions/381208/ienumerablet-as-return -type   -  person Mauricio Scheffer    schedule 19.01.2010
comment
возможный дубликат ienumerablet-as-return-type   -  person nawfal    schedule 31.10.2013


Ответы (14)


Это действительно зависит от того, почему вы используете этот конкретный интерфейс.

Например, IList<T> имеет несколько методов, которых нет в IEnumerable<T>:

  • IndexOf(T item)
  • Insert(int index, T item)
  • RemoveAt(int index)

и свойства:

  • T this[int index] { get; set; }

Если вам каким-либо образом нужны эти методы, то непременно верните IList<T>.

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

person Jon Limjap    schedule 02.07.2009
comment
@Jon FDG рекомендует использовать Collection ‹T› или ReadOnlyCollection ‹T› в качестве возвращаемого значения для типов коллекций, см. Мой ответ. - person Sam Saffron; 03.07.2009
comment
Вам не ясно, что вы имеете в виду в своем последнем предложении, когда говорите, что оно спасет CLR. Что его спасет, используя IEnumerable vs. IList? Вы можете пояснить это? - person PositiveGuy; 07.12.2012
comment
@CoffeeAddict Через три года после этого ответа, я думаю, вы правы - последняя часть расплывчата. Если метод, который ожидает IList ‹T› в качестве параметра, получает IEnumerable ‹T›, IEnumerable должен быть вручную заключен в новый List ‹T› или другой реализатор IList ‹T›, и эта работа не будет выполнена CLR для вас. Напротив - метод, ожидающий, что IEnumerable ‹T› получит IList ‹T›, может сделать некоторую распаковку, но, оглядываясь назад, может и не понадобиться, потому что IList ‹T› реализует IEnumerable ‹T›. - person Jon Limjap; 10.12.2012

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

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

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

Имейте в виду, что LINQ содержит несколько уловок в рукаве и оптимизирует определенные вызовы в зависимости от типа, для которого они выполняются. Так, например, если вы выполняете Count, а базовая коллекция - это список, он НЕ будет проходить через все элементы.

Лично для ORM я бы, вероятно, придерживался Collection<T> в качестве возвращаемого значения.

person Sam Saffron    schedule 03.07.2009
comment
Рекомендации для коллекций содержат более подробный список того, что можно и что нельзя делать. - person Chaquotay; 22.03.2016

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

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

person Mel    schedule 17.12.2009
comment
Я пришел к противоположному выводу: нужно принимать определенные типы, а возвращать общие типы. Однако вы можете быть правы в том, что более широко применимые методы лучше, чем более ограниченные методы. Придется подумать об этом подробнее. - person Dave Cousineau; 09.10.2011
comment
Обоснование принятия общих типов в качестве входных данных состоит в том, что это позволяет вам работать с максимально широким диапазоном входных данных, чтобы получить как можно больше повторного использования компонента. С другой стороны, поскольку вы уже точно знаете, какой объект у вас под рукой, нет особого смысла его маскировать. - person Mel; 14.10.2011
comment
Я думаю, что согласен с более общими параметрами, но каковы ваши доводы в пользу возврата чего-то менее общего? - person Dave Cousineau; 20.10.2011
comment
Хорошо, позволь мне попробовать по-другому. Зачем вы выбрасываете информацию? Если вас интересует только результат IEnumerable ‹T›, вам как-то больно знать, что это IList ‹T›? Нет, это не так. В некоторых случаях это может быть лишняя информация, но она не причинит вам вреда. Теперь о пользе. Если вы вернете List или IList, я сразу могу сказать, что коллекция уже получена, чего я не могу узнать с помощью IEnumerable. Это может быть, а может и не быть полезной информацией, но опять же, почему вы выбрасываете информацию? Если вы знаете дополнительную информацию о чем-то, поделитесь ею. - person Mel; 20.10.2011
comment
Оглядываясь назад, я удивлен, что никто никогда не звонил мне по этому поводу. IEnumerable БУДЕТ уже получен. Однако IQueryable может быть возвращен в ненумерованном состоянии. Так что, возможно, лучшим примером будет возврат IQueryable против IEnumerable. Возврат более конкретного IQueryable позволит выполнить дальнейшую композицию, тогда как возвращение IEnumerable приведет к немедленному перечислению и уменьшению возможности компоновки метода. - person Mel; 20.01.2014
comment
Я не уверен, что это правильно. Многие методы LINQ выполняют отложенную оценку и возвращают IEnumerable. Нет никакой гарантии, что данные были пронумерованы. - person Nick Udell; 03.06.2015
comment
Я думаю, проблема в том, что если вы добавляете дополнительные условия поверх IEnumerable, он может оценивать до этого момента ДО применения дополнительных условий, даже если он еще не был перечислен. Я думаю, что это может зависеть от конкретной реализации, но я не заглядывал внутрь, чтобы убедиться. Я думаю, что самым безопасным является предположение, что IQueryable - это честная игра для дальнейшего уточнения без штрафных санкций, но этого нельзя сказать об IEnumerable. Вы можете изменить его, но вы не можете гарантировать без штрафных санкций. - person Mel; 03.06.2015
comment
Я буду продолжать бить эту лошадь. Я понимаю, что мог бы быть яснее. IEnumerable не обязательно уже был оценен. И IQueryable, и IEnumerable поддерживают отложенное выполнение. Основное отличие состоит в том, что условия, привязанные к IQueryable, могут по-прежнему попадать в SQL-запрос, если ваш IQueryable был получен из ORM. IEnumerable, возможно, еще не был оценен, но добавленные к нему дополнительные предикаты НЕ будут попадать в запрос и будут применяться во втором раунде после первоначального извлечения. Вот ... Думаю, теперь это яснее. - person Mel; 10.06.2015

Это зависит...

Возврат наименее производного типа (IEnumerable) оставит вам наибольшую свободу действий для изменения базовой реализации в дальнейшем.

Возврат более производного типа (IList) предоставляет пользователям вашего API больше операций над результатом.

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

person jerryjvl    schedule 02.07.2009
comment
хороший общий ответ, поскольку он применим и к другим методам. - person user420667; 26.10.2015
comment
Я знаю, что этот ответ очень старый ... но он, похоже, противоречит документации: если ни интерфейс IDictionary ‹TKey, TValue›, ни интерфейс IList ‹T› не соответствуют требованиям требуемой коллекции, создайте новый класс коллекции из Вместо этого интерфейс ICollection ‹T› для большей гибкости. Похоже, это означает, что предпочтение следует отдавать более производному типу. (msdn.microsoft.com/en-us/ библиотека / 92t2ye13 (v = vs.110) .aspx) \ - person DeborahK; 09.12.2015

Одна вещь, которую следует учитывать, заключается в том, что если вы используете оператор LINQ с отложенным выполнением для генерации вашего IEnumerable<T>, вызов .ToList() перед возвратом из вашего метода означает, что ваши элементы могут быть повторены дважды - один раз для создания списка и один раз, когда вызывающий перебирает, фильтрует или преобразует возвращаемое значение. На практике я предпочитаю избегать преобразования результатов LINQ-to-Objects в конкретный список или словарь до тех пор, пока мне это не понадобится. Если моему вызывающему абоненту нужен List, это можно сделать одним простым вызовом метода - мне не нужно принимать это решение за них, и это делает мой код немного более эффективным в случаях, когда вызывающий просто выполняет foreach.

person Joel Mueller    schedule 02.07.2009
comment
@Joel Mueller, я обычно все равно вызываю для них ToList (). Мне вообще не нравится открывать IQueryable остальным моим проектам. - person KingNestor; 02.07.2009
comment
Я больше имел в виду LINQ-to-Objects, где IQueryable обычно не играет роли. Когда задействована база данных, ToList () становится более необходимым, потому что в противном случае вы рискуете закрыть свое соединение до итерации, что не очень хорошо работает. Однако, когда это не проблема, довольно легко предоставить IQueryable как IEnumerable, не вызывая дополнительной итерации, если вы хотите скрыть IQueryable. - person Joel Mueller; 02.07.2009

List<T> предлагает вызывающему коду гораздо больше возможностей, таких как изменение возвращаемого объекта и доступ по индексу. Итак, вопрос сводится к следующему: в конкретном случае использования вашего приложения ХОТИТЕ ли вы поддерживать такое использование (предположительно, возвращая только что созданную коллекцию!) Для удобства вызывающего абонента - или вам нужна скорость для простого случая, когда все вызывающей стороне необходимо выполнить цикл по коллекции, и вы можете безопасно вернуть ссылку на реальную базовую коллекцию, не опасаясь, что это приведет к ее ошибочным изменениям и т. д.?

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

person Alex Martelli    schedule 02.07.2009
comment
безопасно вернуть ссылку на реальную базовую коллекцию, не опасаясь, что это приведет к ее ошибочному изменению - даже если вы вернете IEnumerable ‹T›, не могли бы они просто вернуть ее в список ‹T› и изменить? - person Kobi; 02.07.2009
comment
Не каждый IEnumarable ‹T› также является списком ‹T›. Если возвращаемый объект не относится к типу, который наследуется от List ‹T› или реализует IList ‹T›, это приведет к InvalidCastException. - person lowglider; 02.07.2009
comment
List ‹T› имеет проблему, заключающуюся в том, что он привязывает вас к определенной реализации. Коллекция ‹T› или ReadOnlyCollection ‹T› предпочтительнее - person Sam Saffron; 03.07.2009

Не все так просто, когда речь идет о возвращаемых значениях, а не о входных параметрах. Когда это входной параметр, вы точно знаете, что вам нужно делать. Итак, если вам нужно иметь возможность перебирать коллекцию, вы берете IEnumberable, тогда как если вам нужно добавить или удалить, вы берете IList.

В случае с возвращаемым значением сложнее. Чего ожидает ваш собеседник? Если вы вернете IEnumerable, он априори не будет знать, что может сделать из него IList. Но если вы вернете IList, он будет знать, что может перебирать его. Итак, вы должны принять во внимание, что ваш вызывающий абонент собирается делать с данными. Функциональные возможности, которые требуются / ожидаются от вашего вызывающего абонента, - это то, что должно регулировать при принятии решения о том, что возвращать.

person JP Alioto    schedule 02.07.2009

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

IEnumerable неэффективен для подсчета элементов

Если коллекция предназначена только для чтения или модификация коллекции контролируется Parent, то возвращать IList только для Count - не лучшая идея.

В Linq есть Count() метод расширения на IEnumerable<T>, который внутри CLR будет сокращать до .Count, если базовый тип имеет IList, поэтому разница в производительности незначительна.

Как правило, я считаю (мнение), что лучше возвращать IEnumerable, где это возможно, если вам нужно делать дополнения, затем добавьте эти методы в родительский класс, в противном случае потребитель затем управляет коллекцией в модели, что нарушает принципы, например. manufacturer.Models.Add(model) нарушает закон Деметры. Конечно, это всего лишь рекомендации, а не жесткие и быстрые правила, но до тех пор, пока вы полностью не поймете их применимость, лучше слепо следовать, чем не следовать вовсе.

public interface IManufacturer 
{
     IEnumerable<Model> Models {get;}
     void AddModel(Model model);
}

(Примечание: при использовании nNHibernate вам может потребоваться сопоставление с частным IList с использованием разных аксессоров.)

person Community    schedule 02.07.2009

TL; DR; - резюме

  • Если вы разрабатываете собственное программное обеспечение, используйте определенный тип (например, List) для возвращаемых значений и наиболее общий тип для входных параметров даже в случае коллекций.
  • Если метод является частью общедоступного API распространяемой библиотеки, используйте интерфейсы вместо конкретных типов коллекций, чтобы ввести как возвращаемые значения, так и входные параметры.
  • Если метод возвращает коллекцию, доступную только для чтения, покажите это, используя IReadOnlyList или IReadOnlyCollection в качестве типа возвращаемого значения.

Дополнительно

person Ali Bayat    schedule 06.03.2020

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

person Usman Masood    schedule 02.07.2009

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

Однако, если вам нужно подсчитать количество элементов, не забывайте, что между IEnumerable и IList есть еще один уровень - ICollection.

person arbiter    schedule 02.07.2009

Возможно, я немного ошибаюсь, потому что пока никто этого не предлагал, но почему бы вам не вернуть (I)Collection<T>?

Насколько я помню, Collection<T> был предпочтительным типом возвращаемого значения по сравнению с List<T>, потому что он абстрагирует реализацию. Все они реализуют IEnumerable, но для меня это звучит слишком низко для работы.

person Tiberiu Ana    schedule 02.07.2009

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

IEnumerable неэффективен для подсчета элементов или получения определенного элемента в коллекции.

List - это коллекция, которая идеально подходит для поиска определенных элементов, простого добавления или удаления элементов.

Обычно я стараюсь использовать List, где это возможно, так как это дает мне больше гибкости.

Используйте List<FooBar> getRecentItems() вместо IList<FooBar> GetRecentItems()

person Stuart    schedule 02.07.2009

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

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

Помните, что перемещение UP к коллекции из IEnumerable в интерфейсе будет работать, переход к IEnumerable из коллекции нарушит существующий код.

Если все эти мнения кажутся противоречивыми, это потому, что решение является субъективным.

person Sprague    schedule 04.10.2012