IEnumerable <T>в качестве типа возврата - PullRequest
28 голосов
/ 19 декабря 2008

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

Ну, я всегда руководствовался правилом «прими меньшее, что можешь, но верни максимум».

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

Ответы [ 10 ]

29 голосов
/ 19 декабря 2008

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

1) Есть ли что-то не так с возвратом IEnumerable

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

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

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

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

27 голосов
/ 19 декабря 2008

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

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

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

4 голосов
/ 19 декабря 2008

О вашем принципе: «прими меньшее, что можешь, но верни максимум».

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

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

4 голосов
/ 19 декабря 2008

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

3 голосов
/ 06 декабря 2011

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

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

Сказанное выше, есть несколько других вещей, которые следует учитывать

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

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

Относительно ленивых оценок LINQ, я уверен, что 95% пользователей C # не понимают насильственные действия. Это так не ооо. ОО способствует конкретным изменениям состояния при вызове метода. Ленивая оценка LINQ способствует изменениям состояния среды выполнения в шаблоне оценки выражений (не всегда за этим следуют непрофессионалы).

2 голосов
/ 19 ноября 2010

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

2 голосов
/ 19 декабря 2008

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

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

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

1 голос
/ 19 декабря 2008

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

0 голосов
/ 15 января 2014

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

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

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

0 голосов
/ 19 декабря 2008

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

...