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


98

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

public IEnumerable<FooBar> GetRecentItems()

или

public IList<FooBar> GetRecentItems()

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


1
Вы спрашиваете о List <T> или IList <T>? Название и вопрос говорят о разном ...
Фредрик Мёрк

Когда это возможно, пользовательский интерфейс IEnumerable или Ilist instaead типа Concerete.
Усман Масуд

2
Я возвращаю коллекции как List <T>. Я не вижу необходимости возвращать IEnumberable <T>, поскольку вы можете извлечь это из List <T>
Чак Конвей


возможный дубликат ienumerablet-as-return-type
nawfal

Ответы:


45

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

Например, 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 от рассмотрения любых необходимых преобразований, тем самым оптимизируя скомпилированный код.


2
@Jon FDG рекомендует использовать Collection <T> или ReadOnlyCollection <T> в качестве возвращаемого значения для типов коллекций, см. Мой ответ.
Сэм Саффрон,

Вам не ясно, что вы имеете в виду в своем последнем предложении, когда говорите «это спасет CLR». Что его спасет, используя IEnumerable или IList? Вы можете пояснить это?
PositiveGuy

1
@CoffeeAddict Через три года после этого ответа, я думаю, вы правы - последняя часть расплывчата. Если метод, который ожидает IList <T> в качестве параметра, получает IEnumerable <T>, IEnumerable должен быть вручную обернут в новый List <T> или другой разработчик IList <T>, и эта работа не будет выполнена CLR для вас. Напротив - метод, ожидающий, что IEnumerable <T> получит IList <T>, может потребовать некоторой распаковки, но, оглядываясь назад, может и не понадобиться, потому что IList <T> реализует IEnumerable <T>.
Джон Лимджап

69

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

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

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

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

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


14
В Руководстве по сбору средств содержится более подробный список того, что можно и что нельзя делать.
Chaquotay,

27

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

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


1
Я пришел к противоположному выводу: нужно принимать определенные типы и возвращать общие типы. Однако вы можете быть правы в том, что более широко применимые методы лучше, чем более ограниченные методы. Мне придется подумать об этом подробнее.
Дэйв Кузино

3
Обоснование принятия общих типов в качестве входных данных состоит в том, что это позволяет вам работать с максимально широким диапазоном входных данных, чтобы получить как можно больше повторного использования компонента. С другой стороны, поскольку вы уже точно знаете, какой объект у вас под рукой, нет особого смысла его маскировать.
Mel

1
Я думаю, что согласен с более общими параметрами, но каковы ваши аргументы в пользу возврата чего-то менее общего?
Дэйв Кузино

7
Хорошо, позволь мне попробовать по-другому. Почему вы выбрасываете информацию? Если вас интересует только результат IEnumerable <T>, не повредит ли вам каким-либо образом узнать, что это IList <T>? Нет, это не так. В некоторых случаях это может быть лишняя информация, но она не причинит вам вреда. Теперь о пользе. Если вы вернете List или IList, я сразу могу сказать, что коллекция уже получена, чего я не могу узнать с IEnumerable. Это может быть, а может и не быть полезной информацией, но опять же, почему вы выбрасываете информацию? Если вы знаете дополнительную информацию о чем-то, поделитесь ею.
Mel

Оглядываясь назад, я удивлен, что никто никогда не звонил мне по этому поводу. IEnumerable БУДЕТ уже получен. Однако IQueryable может быть возвращен в ненумерованном состоянии. Так что, возможно, лучшим примером будет возврат IQueryable против IEnumerable. Возврат более конкретного IQueryable позволит выполнить дальнейшую композицию, тогда как возвращение IEnumerable приведет к немедленному перечислению и уменьшит возможность компоновки метода.
Mel

23

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

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

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

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


хороший общий ответ, поскольку он применим и к другим методам.
user420667

1
Я знаю, что этот ответ очень старый ... но, похоже, он противоречит документации: «Если ни интерфейс IDictionary <TKey, TValue>, ни интерфейс IList <T> не соответствуют требованиям требуемой коллекции, создайте новый класс коллекции из вместо этого интерфейс ICollection <T> для большей гибкости "Это, по-видимому, означает, что предпочтение следует отдавать более производному типу. ( msdn.microsoft.com/en-us/library/92t2ye13(v=vs.110).aspx ) \
DeborahK,

11

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


@Joel Mueller, я обычно все равно вызываю для них ToList (). Мне вообще не нравится открывать IQueryable остальным моим проектам.
KingNestor 02

4
Я больше имел в виду LINQ-to-Objects, где IQueryable обычно не играет роли. Когда задействована база данных, ToList () становится более необходимым, потому что в противном случае вы рискуете закрыть соединение до итерации, что не очень хорошо работает. Однако, когда это не проблема, довольно легко предоставить IQueryable как IEnumerable, не вызывая дополнительной итерации, если вы хотите скрыть IQueryable.
Joel Mueller

9

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

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


«безопасно вернуть ссылку на реальную базовую коллекцию, не опасаясь, что это приведет к ее ошибочному изменению» - даже если вы вернете IEnumerable <T>, не могли бы они просто вернуть ее обратно в List <T> и изменить?
Коби

Не каждый IEnumarable <T> также является List <T>. Если возвращаемый объект не относится к типу, который наследуется от List <T> или реализует IList <T>, это приведет к InvalidCastException.
lowglider 02

2
List <T> имеет проблему, заключающуюся в том, что он привязывает вас к конкретной реализации. Предпочтительны Collection <T> или ReadOnlyCollection <T>
Сэм Саффрон,

4

Я думаю, вы можете использовать и то, и другое, но у каждого есть применение. В основном 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 с использованием разных аксессоров.)


2

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

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


0

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


0

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

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


0

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

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


0

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

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

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

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

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


0

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

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

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

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


0

TL; DR; - резюме

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

Больше

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.