IEnumerable ‹T› как возвращаемое значение, отложенное выполнение и N-уровневые приложения

Во-первых, я не считаю этот вопрос дублированием этих SO-вопросов:

Должен ли я всегда возвращать IEnumerable ‹T› вместо IList ‹T›? и IEnumerable ‹T› в качестве возвращаемого типа

Как мы все знаем, одной из основных целей введения нескольких уровней является уменьшение связи.
Мы должны определить некоторый интерфейс для доступа к данным, и наш BL не должен заботиться о деталях реализации DAL. Если упомянутый интерфейс возвращает IEnumerable<T> BL не знает, является ли это просто статическим IEnumerable или чем-то, что имеет отложенное выполнение. В то же время эта конкретная деталь может значительно повлиять на производительность и требует разного кодирования в зависимости от реализации.
Ну, можно вызывать .ToList() для каждого IEnumerable в ситуациях, когда мы собираемся повторять сборку несколько раз. Но это снижает производительность статических коллекций из-за ненужного создания экземпляров нового списка.

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


person Pavel Voronin    schedule 28.01.2013    source источник
comment
Обратите внимание: если вы используете foreach для итерации IEnumerable ‹T›, который на самом деле является списком ‹T› или простым массивом, это очень быстро. Реализация делает некоторые оптимизации. (Вы можете проверить это, используя секундомер для измерения времени.)   -  person Matthew Watson    schedule 28.01.2013
comment
Конечно, мне кажется, что это дубликат. Особенно по поводу второго связанного вами вопроса. Принятый ответ на этот вопрос достаточно хорошо решает ваши опасения. Ключ в том, что это зависит от обстоятельств. Если отложенное выполнение вызовет проблемы с производительностью, пусть ваш DAL вернет конкретный список.   -  person Jim Mischel    schedule 28.01.2013
comment
Если вы используете .Net 4.5, вы можете использовать IReadOnlyList<T>.   -  person svick    schedule 28.01.2013
comment
@ Джиммишель. Я согласен, но если мы хотим обеспечить модульность и возможность изменения, как мы можем ожидать от DAL большего, чем простой интерфейс?   -  person Pavel Voronin    schedule 28.01.2013


Ответы (3)


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

person Servy    schedule 28.01.2013
comment
чтобы знать, что повторение его несколько раз для IEnumerable не приведет к негативным последствиям. Что ты имеешь в виду ? - person Tigran; 28.01.2013
comment
@Tigran Вполне возможно, что IEnumerable может возвращать совершенно разные результаты при многократном повторении, или он может включать запросы к базе данных или выполнение ресурсоемких задач. Поэтому, если дан IEnumerable из неизвестного источника, лучше любой ценой избегать повторения его несколько раз. В определенных ситуациях это означает занесение последовательности в список, чтобы вы могли быть уверены, что повторение ее несколько раз не приведет к этим (возможно, нежелательным) побочным эффектам. Если это уже список, значит, вы сделали ненужную работу. - person Servy; 28.01.2013
comment
Возврат списка имеет свой собственный набор забот ... например, сделал ли получатель защитную копию? - person Matthew Watson; 28.01.2013
comment
@MatthewWatson Конечно. Это не значит, что вы никогда не должны возвращать список ни одним методом. - person Servy; 28.01.2013
comment
@Servy: Я понимаю, вы говорите о декларативном намерении, но OP спрашивает о конкретных компромиссах (возможных) в его приложении. Так что ваш ответ не действительно отвечает на его вопрос. Объявление IEnumerable определяет намерение определить поток данных, но это не совсем означает, что вы можете или не должны выполнять итерацию по нему несколько раз, поскольку все зависит от детали реализации. Итак, вопрос: как лучше спроектировать возврат / передачу значений, чтобы возможно избежать проблем с масштабируемостью между разными уровнями в будущем. - person Tigran; 28.01.2013

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

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

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

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

person Eric Lippert    schedule 28.01.2013
comment
К сожалению, мир не идеален, и мы должны сохранять баланс между открытостью архитектуры (чем меньше усилий нам нужно для решения радикальных изменений требований, тем лучше) и YAGNI. Я думаю, что это смесь предположений и опыта, которая помогает отличить правильное от неправильного. Здесь я бы добавил, что IEnumerable оставляет некоторую гибкость в отношении запросов данных. Если рассматривать BL как уровень CRUD для клиентов, IEnumerable позволяет им запрашивать определенные данные. Таким образом, мы можем сохранить BL простым, не перегружая его множеством методов для различных сценариев чтения данных. - person Pavel Voronin; 28.01.2013
comment
С другой стороны, как только вы захотите переместить BL на сервер, ситуация изменится. Набор методов - большое преимущество. Теперь учтите, что вы должны реорганизовать старое приложение с высокой степенью сопряжения до N-уровневого приложения, а затем переместить некоторые его части на сервер приложений. Далее я добавляю, что разработчиками клиентских приложений будут другие ребята, и наша команда должна довольно быстро отвечать на их запросы. И все это медленно развивающийся процесс. Если бы у меня был опыт, что правильно, а что неправильно, я бы даже не спрашивал =) - person Pavel Voronin; 28.01.2013

Я нашел хорошее решение: все возвращаемые типы (входящие интерфейсы) в DAL не могут быть IEnumerable<> (или не могут быть типами интерфейса). Но можно использовать в BL. Это может быть решение архитектора.

person Alexey Ripenko    schedule 28.01.2013