Вопрос

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

Ну а я всегда руководствовался правилом «принимай минимум, но возвращай максимум».

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

Это было полезно?

Решение

На самом деле это вопрос из двух частей.

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

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

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

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

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

Другие советы

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

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

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

О вашем принципе:«Принимайте минимум, но возвращайте максимум».

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

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

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

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

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

Как было сказано выше, есть еще несколько вещей, которые следует учитывать.

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

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

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

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

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

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

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

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

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

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

Любое возвращаемое значение представляет собой снимок.Это может быть текущее содержимое IEnumerable, и в этом случае, если оно кэшировано, оно должно быть клоном кэшированной копии;если он должен быть более динамичным (например, результаты запроса sql), используйте возврат доходности;однако разрешение контейнеру изменяться по своему желанию и предоставление таких методов, как Count и Indexer, — это верный путь к катастрофе в многопоточном мире.Я даже не разобрался с возможностью вызывающей стороны вызывать Add или Delete в контейнере, которым должен управлять ваш код.

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top