Linq Должен ли я возвращать List‹T› или IEnumerable‹T›, когда я еще могу сделать больше позже

У меня есть несколько методов, которые возвращают список T, например, GetAllEvents. В некоторых случаях мне нужно затем отфильтровать этот список событий (или любой другой мой список) по дате или какому-либо другому свойству элементов.

Я знаю, что запросы LINQ могут быть «связаны» или иметь x строк, которые дополнительно уточняют их, и запрос не будет выполняться до определенного момента, когда вам нужно будет фактически использовать их в операторе, отличном от linq (пожалуйста, поправьте меня, если я ошибаюсь на этом убеждении.)

Мой вопрос: если мой метод GetAllXXX возвращает список того, что я получаю, является ли метод .ToList(), который я использую в конце моего кода GetAllXXX, выполняющего LINQ? Должен ли я вместо этого вернуть IEnumerable? Если только для тех случаев, когда мне нужно сделать что-то еще для «результатов» ДО того, как запрос будет фактически запущен.

Вот пример моего беспокойства: у меня, скажем, 1000 событий. GetAllEvents извлечет все 1000 и даст мне их список. Затем, в зависимости от того, на какой странице находится пользователь, могут отображаться события только на сегодня, на этой неделе или определенной категории. В идеале, к тому времени, когда я показываю пользователю 5 событий, происходящих сегодня, я действительно не хочу передавать все 1000 по сети, а затем урезать их до 5, которые они действительно хотят. Да, я знаю, что на данный момент это все на стороне сервера, но если он все еще выделяет память для 1000, я пытаюсь этого избежать.

Любые указатели или предложения?


person Grandizer    schedule 26.04.2011    source источник
comment
Не возвращать List<T>. Но вы можете подумать о возврате IList<T>.   -  person R. Martinho Fernandes    schedule 26.04.2011


Ответы (3)


Вернуть IEnumerable.

Преобразование в List выполняется быстро и безболезненно, плюс вы сохраняете связь своего интерфейса как с реализацией ваших методов, так и с использованием вывода.

Что касается вашего конкретного беспокойства - если будет дорого возвращать все 1000 событий и обрабатывать их на клиенте, вам следует подумать о некоторой фильтрации на сервере. У вас все еще может быть метод, который возвращает все события, но есть специализированные/оптимизированные версии, которые возвращают наиболее частые запросы. События сегодняшнего дня могут быть хорошим примером.

person ChrisF    schedule 26.04.2011
comment
Я абсолютно согласен. Однако стоит принять во внимание одно небольшое предостережение: если вы в конечном итоге не преобразуете IEnumerable в массив или список и выполните несколько операций с этим IEnumerable, вы можете непреднамеренно выполнить запрос несколько раз, что может создать серьезную проблему с производительностью. Иногда неплохо иметь тип возвращаемого значения IEnumerable, но все же преобразовать его в массив перед возвратом объекта. - person Phil; 26.04.2011

Если вы превращаете последовательность в список на сервере, то вы используете время и память на сервере, а затем передаете все это по сети, а затем используете больше времени и памяти на клиенте для фильтрации списка.

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

Если вы хотите выполнить фильтрацию на сервере, вы можете (1) создать собственный API, который представляет общие фильтры (просто), или (2) вместо этого вернуть IQueryable и реализовать поставщика LINQ. (трудно, но мощно.) IQueryable позволяет создать запрос на стороне клиента, отправить запрос по сети на сервер, выполнить запрос на сервере, а затем предоставить только те результаты, которые нужны клиенту.

Мой коллега Мэтт Уоррен написал целую серию статей о том, как реализовать IQueryable; Я бы начал с этого.

person Eric Lippert    schedule 26.04.2011

Ответ Эрика Липперта хорош, но обратите внимание, что вам не нужно реализовывать весь IQueryable, если вы просто хотите обеспечить некоторую фильтрацию на стороне сервера (даже пользовательскую фильтрацию). Вы можете создать более простой LINQ-совместимый API, реализовав только те функции LINQ, которые вы действительно будете использовать. Например, рассмотрим определение

interface IOneTripEnumerable<T> : IEnumerable<T>

который предоставляет только LINQ-совместимый метод Where с возвращаемым типом IOneTripEnumerable

Реализация IOneTripEnumerable.Where вернет новый объект, который также реализует IOneTripEnumerable и просто сохранит фильтр как член данных. Когда вызывается IOneTripEnumerable.GetEnumerator, вы можете упаковать фильтры и отправить их на сервер, а затем получить отфильтрованные результаты за один цикл.

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

Если у вас есть больше времени и вы видите необходимость, вы можете оптимизировать дальнейшую оптимизацию, добавив дополнительные методы LINQ, но просто взяв под контроль Where (чтобы фильтрация происходила на сервере) и GetEnumerator (чтобы получить все результаты за один раунд). поездка) может дать вам довольно хорошие результаты при низких затратах на реализацию. Вам не нужно реализовывать весь IQueryable. (Обратите внимание, что Count, Any и Take также являются очень хорошими кандидатами для круговой оптимизации и их тривиально реализовать).

person AJS    schedule 26.04.2011
comment
Это хорошая точка; на самом деле большинство поставщиков LINQ не реализуют в полной мере все возможные функции IQueryable. - person Eric Lippert; 26.04.2011