Подтвердить что ты не робот

Должен ли я одобрить IEnumerable <T> или массивы?

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

Public ReadOnly Property GetValues() As IEnumerable(Of Integer)
    Get
        'code to return the values'
    End Get
End Property

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

Мой вопрос заключается в том, что я нарушаю любые принципы проектирования, возвращая IEnumerable<T> вместо определенных типов (например: List<T>, HashSet<T>, Stack<T> или Array s)?

4b9b3361

Ответ 1

Я обычно предпочитаю IEnumerable<T>. Главное - спросить себя, какая фактическая (даже минимальная) функциональность возвращается из метода (или передается ему, в случае аргумента метода).

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

Ответ 2

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

Я просто хотел добавить это, так как вы указали коллекцию только для чтения в своем исходном вопросе, вы можете принудительно выполнить это, вызвав .ToList().AsReadOnly() в свой экземпляр IEnumerable<T>, прежде чем возвращать его. Это создаст конкретный экземпляр ReadOnlyCollection<T> для возврата. Тип возврата должен быть IEnumerable<T>, так как вызывающему абоненту не нужно знать, что вы специально возвращаете ReadOnlyCollection<T>, но таким образом вы не позволяете вызывающему абоненту стрелять в ногу.

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

Ответ 3

Лично я считаю, что возвращение IEnumerable<T> из API - это сильный намек на то, что реализация может использовать ленивую оценку.

Зная, что ленивая оценка может быть использована, может быть важна для вызывающего, поскольку это означает, что:

  • повторение результата более одного раза (например, для получения счета, затем доступа к данным) приведет к тому, что оценка будет выполнена более одного раза.

  • исключения могут быть выбраны из API при повторении результата:

например.

IEnumerable<MyObject> LazyEvaluatedApi()
{
}

...

IEnumerable<MyObject> result = LazyEvaluatedApi();
... 
foreach(MyObject item in result) // Exception from LazyEvaluatedApi may be thrown here.
{
}

Если вы не думаете, что какая-либо реализация должна будет использовать ленивую оценку (например, API уровня доступа к данным), я бы предпочел вернуться ICollection<T> или IList<T>. Помимо предоставления вызывающему абоненту свойства Count (ICollection<T> и IList<T>) и индексатора (только IList<T>), вы также четко указываете, что не будет никакой ленивой оценки.

При возврате ICollection<T> или IList<T> ваша конкретная реализация, скорее всего, вернет List<T>. Если важно, чтобы список был только для чтения, верните List<T>.AsReadOnly()

Ответ 4

Это зависит от того, что вы хотите сделать и что вы намерены "нарушать". Это зависит от:

  • Если вы хотите, чтобы ваши коллекции, возвращаемые вашими объектами, могли быть изменены внешним кодом, вы должны возвращать модифицируемые типы aka List < > , HashSet < > Stack < > или любые другие, которые допускают модификации
  • Если вы хотите, чтобы ваш пользователь "коллекции" просто перебирал элементы коллекции, но без изменения самой коллекции, это правильный выбор.

Помните, что многие хорошие гуру и принципы дизайна предпочитают возвращать IEnumerable < > over modifiable-collection-ready

Ответ 5

Возврат IEnumerable<T> в порядке. Хотя следите за тем, что если T является ссылочным типом, то возможно, чтобы вызывающий изменил объект.

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

Ответ 6

Если вам не нужны какие-либо дополнительные функции, предоставляемые List<T>, HashSet<T> и т.д., тогда возврат IEnumerable<T> прекрасен, imo.

Возвратите любой тип, который вы считаете наиболее подходящим; если вам нужно только перебирать коллекции или делать с ними LINQ-y, тогда IEnumerable<T> хорош в большинстве ситуаций.

Когда вам нужно возвращать "более богатые" типы, рассмотрите возможность возврата интерфейса, а не конкретного типа - IList<T>, а не List<T>, ISet<T>, а не HashSet<T> и т.д.), чтобы ваша реализация могла изменения в будущем, если необходимо, без нарушения кода вызова.

Ответ 7

Это зависит. Если вы предполагаете, что использование вашего метода является просто перечислением, то IEnumerable является правильным выбором.

Если важны конкретные функции, такие как поиск индекса, то вы можете быть более конкретными.

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

Ответ 8

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