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

#c# #linq #memory-management

#c# #linq #управление памятью

Вопрос:

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

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

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

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

Какие-либо указания или предложения?

Комментарии:

1. Не возвращайте List<T> . Но вы можете подумать о возвращении IList<T> .

Ответ №1:

Верните IEnumerable .

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

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

Комментарии:

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

Ответ №2:

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

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

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

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

Ответ №3:

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

 interface IOneTripEnumerable<T> : IEnumerable<T>
  

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

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

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

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

Комментарии:

1. Это хороший момент; на самом деле большинство поставщиков LINQ не реализуют в полной мере все возможные функции, доступные IQueryable.