День 1618. #ЗаметкиНаПолях
LINQ: Select.Where или Where.Select?
LINQ — очень мощный инструмент для запросов данных. Поскольку большинство функций построены на основе IEnumerable<T> и в большинстве случаев он также возвращает IEnumerable<T>, очень легко объединять несколько функций в цепочку. Возникает вопрос: есть ли разница между Select.Where или Where.Select?
Сначала замечу, что далее мы рассматриваем только коллекции «в памяти», и не IQueryable<T>. Там порядок зависит в основном от провайдера, поэтому Entity Framework и LINQ to SQL могут давать разные результаты.
Рассмотрим следующий пример кода:
var users = GetAllUsers();
// Вариант 1
users.Where(u => u.IsStudent)
.Select(u =>
new { u.FirstName, u.LastName, u.IsStudent });
// Вариант 2
users.Select(u =>
new { u.FirstName, u.LastName, u.IsStudent })
.Where(u => u.IsStudent);
Результат обоих запросов будет одинаковым. Но это не означает, что они делают одно и то же. Первый вариант сначала отфильтрует всех пользователей, которые не являются студентами, а затем создаст анонимный тип. Второй вариант сначала создаст анонимный тип для всех пользователей, а затем отфильтрует тех, кто не является студентами. Какой запрос быстрее?
Скорее всего, первый, т.к. второй вариант создаст анонимный тип для всех пользователей, даже для тех, кто не является студентом. Т.е. второму варианту придётся проделать больше работы. Для больших списков разница может быть значительна. Как правило, всегда нужно пытаться отфильтровать как можно больше, прежде чем начинать создавать новые объекты. Аналогично с OrderBy. Если список предварительно не фильтруется, OrderBy должен будет проверить гораздо больше записей.
А теперь рассмотрим такой пример:
// вариант 1
users.Where(u => u.IsStudent && u.Age > 30);
// вариант 2
users.Where(u => u.IsStudent)
.Where(u => u.Age > 30);
Опять же семантически они делают одно и то же. Результат будет одинаковым, но способ его достижения - разным. Первый вариант сразу проверит оба условия. Второй вариант (хотя это и не очевидно) также пройдёт по списку только один раз, но здесь возникнут дополнительные накладные расходы на вызов нескольких функций. Кстати, автор оригинальной статьи (см. источник ниже) считает, что этими расходами можно пренебречь. Однако, судя по моим бенчмаркам (см. картинку ниже) разница в некоторых средах может быть довольно существенной (хотя, в .NET 8 выполнение заметно оптимизировали).
Если всё же не обойтись без нескольких предложений Where, имеет смысл поставить лучший фильтр в начале, либо попробовать реорганизовать все фильтры в один метод:
private bool IsStudentAndOlderThan30(User user)
{
return user.IsStudent && user.Age > 30;
}
users.Where(IsStudentAndOlderThan30);
Источник: https://steven-giesel.com/blogPost/57ed9867-4afd-4d02-9f35-e0941bc6f715