IEnumerable <T> как возвращаемый тип

Есть ли проблема с использованием IEnumerable<T> в качестве возвращаемого типа? FxCop жалуется на возвращение List<T> (он рекомендует возвращать Collection<T>).

Ну, я всегда руководствовался правилом "принимаю как можно меньше, но возвращаю максимум".

С этой точки зрения возвращение IEnumerable<T> - это плохо, но что мне делать, когда я хочу использовать "ленивый поиск"? Кроме того, ключевое слово yield является таким хорошим дополнением.

Ответ 1

Это действительно вопрос из двух частей.

1) Есть ли что-то неправильное с возвратом IEnumerable <T>

Нет ничего вообще. Фактически, если вы используете итераторы С#, это ожидаемое поведение. Преобразование его в список <T> или еще один класс коллекции упреждающе не является хорошей идеей. Это делает предположение о шаблоне использования вашим абонентом. Мне нехорошо предполагать что-либо о вызывающем. У них могут быть веские причины, по которым они хотят IEnumerable <T> . Возможно, они хотят преобразовать его в совершенно другую иерархию коллекций (в этом случае конвертирование в список будет потрачено впустую).

2) Существуют ли какие-либо обстоятельства, когда может быть предпочтительнее возвращать нечто, отличное от IEnumerable <T> ?

Да. Хотя это не очень хорошая идея, чтобы принять много о ваших абонентах, совершенно нормально принимать решения, основанные на вашем собственном поведении. Представьте сценарий, в котором у вас был многопоточный объект, который ставил очередь запросов в объект, который постоянно обновлялся. В этом случае возвращает необработанный IEnumerable <T> безответственно. Как только сбор будет изменен, перечислимое будет признано недействительным и приведет к его выполнению. Вместо этого вы можете сделать снимок структуры и вернуть это значение. Скажем в списке <T> форма. В этом случае я просто вернул бы объект как прямую структуру (или интерфейс).

Это, безусловно, более редкий случай.

Ответ 2

Нет, IEnumerable<T> - хорошая вещь, чтобы вернуться сюда, поскольку все, что вы обещаете, - это "последовательность (типизированных) значений". Идеально подходит для LINQ и т.д., И идеально подходит для использования.

Вызывающий может легко помещать эти данные в список (или что-то еще) - особенно с LINQ (ToList, ToArray и т.д.).

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

Ответ 3

IEnumerable в порядке, но у него есть некоторые недостатки. Клиент должен перечислить, чтобы получить результаты. У него нет возможности проверить счетчик и т.д. Список плох, потому что вы подвергаетесь большому контролю; клиент может добавлять/удалять и т.д., и это может быть плохо. Коллекция кажется лучшим компромиссом, по крайней мере, в представлении FxCop. Я всегда использую то, что кажется подходящим в моем контексте (например, если я хочу вернуть коллекцию только для чтения, я выставляю коллекцию как возвращаемый тип и возвращаю List.AsReadOnly() или IEnumerable для ленивой оценки через доходность и т.д.). Возьмите его в каждом конкретном случае

Ответ 4

О вашем принципе: "принимайте как можно меньше, но возвращайте максимум".

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

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

Ответ 5

"Примите как можно меньше, но верните максимум" - это то, что я защищаю. Когда метод возвращает объект, какие оправдания нам не нужно возвращать действительный тип и ограничивать возможности объекта, возвращая базовый тип. Тем не менее возникает вопрос, как мы узнаем, что будет "максимальным" (фактическим типом) при проектировании интерфейса. Ответ очень простой. Только в крайних случаях, когда дизайнер интерфейсов разрабатывает открытый интерфейс, который будет реализован вне приложения/компонента, они не будут знать, каков может быть фактический тип возврата. Умный дизайнер всегда должен учитывать, что должен делать этот метод, и каким должен быть тип оптимального/общего возврата.

например. Если я создаю интерфейс для извлечения вектора объектов, и я знаю, что количество возвращаемых объектов будет переменной, я всегда буду считать, что интеллектуальный разработчик всегда будет использовать список. Если кто-то планирует вернуть массив, я бы поставил под сомнение его возможности, если только он не просто возвращает данные с другого слоя, который у него нет. Вероятно, именно поэтому FxCop выступает за ICollection (общая база для List и Array).

Приведенное выше, есть еще несколько вещей, чтобы рассмотреть

  • если возвращаемые данные должны быть изменяемыми или неизменяемыми

  • если возвращенные данные будут переданы нескольким вызывающим абонентам

Что касается ленивых оценок LINQ, я уверен, что 95% + С# пользователи не понимают завещания. Его так не-о-иш. OO способствует конкретным изменениям состояния при вызове метода. ЛИНЕЙНАЯ оценка LINQ способствует изменениям состояния среды выполнения в шаблоне оценки выражений (не всегда, что не всегда продвигают пользователи).

Ответ 6

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

Но, как указывают другие, у него есть недостаток, который может потребоваться для вызова вызывающего абонента, если ему нужна какая-либо другая информация (например, Count). Метод расширения .NET 3.5 IEnumerable <T> .Count будет перечислять за кулисами, если возвращаемое значение не реализует ICollection <T> , что может быть нежелательным.

Я часто возвращаю IList <T> или ICollection <T> когда результат представляет собой коллекцию - внутри вашего метода может использоваться List <T> и либо вернуть его как есть, либо вернуть List <T> .AsReadOnly, если вы хотите защитить от модификации (например, если вы кэшируете список внутри). AFAIK FxCop вполне доволен любым из них.

Ответ 7

Важным аспектом является то, что при возврате List<T> вы возвращаете ссылку. Это позволяет вызывающему управлять вашим списком. Это обычная проблема - например, бизнес-уровень, который возвращает List<T> на уровень графического интерфейса пользователя.

Ответ 8

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

Ответ 9

Я думаю, что ваше собственное руководство замечательно - если вы можете быть более конкретным о том, что вы возвращаете без удара производительности (вам не нужно, например, создавать список из вашего результата), сделайте это. Но если ваша функция законно не знает, какой тип она найдет, например, если в некоторых ситуациях вы будете работать со списком, а в некоторых с массивом и т.д., То возврат IEnumerable - это "лучший", который вы можете сделать, Подумайте об этом как о "наибольшей общей кратности" всего, что вы, возможно, захотите вернуться.

Ответ 10

Я не могу принять выбранный ответ. Существуют способы решения описанного сценария, но с использованием списка или любого другого, что вы используете, не является одним из них. Когда IEnumerable возвращается, вы должны предположить, что вызывающий может сделать foreach. В этом случае не имеет значения, является ли конкретный тип List или спагетти. На самом деле просто индексирование является проблемой, особенно если элементы удалены.

Любое возвращаемое значение является моментальным снимком. Это может быть текущее содержимое IEnumerable, в этом случае, если он кэширован, он должен быть клоном кешированной копии; если он должен быть более динамичным (например, результирует sql-запрос), используйте return return; тем не менее, разрешая контейнеру мутировать по своему желанию, а методы доставки, такие как Count и indexer, являются рецептом катастрофы в многопоточном мире. Я даже не получил возможность вызова вызывающего абонента "Добавить" или "Удалить" на контейнере, который должен контролировать ваш код.

Также возврат конкретного типа блокирует вас в реализации. Сегодня внутри вы можете использовать список. Завтра, может быть, вы станете многопоточным и хотите использовать контейнер с потоком или массив или очередь или коллекцию значений для словаря или выход запроса Linq. Если вы заблокируете себя в конкретном обратном типе, вам нужно либо сменить кучу кода, либо сделать преобразования перед возвратом.