Какой набор методов определяет интерфейс IQueryable?

«Какой набор методов определяет интерфейс IQueryable?» — вопрос из категории C# Core, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Интерфейс IQueryable<T> наследует от IEnumerable<T> и IQueryable. Его ключевая особенность — отложенное выполнение (deferred execution) и построение запроса в виде дерева выражений (Expression), которое может быть транслировано в другой язык (например, SQL).

Основные свойства, определенные в IQueryable и IQueryable<T>:

  1. Expression — возвращает дерево выражений (System.Linq.Expressions.Expression), которое представляет запрос. Это «описание» запроса, а не его результат.
  2. ElementType — возвращает тип элементов в результирующей последовательности (например, User).
  3. Provider — возвращает объект-поставщик запросов (IQueryProvider), который отвечает за компиляцию дерева выражений и его выполнение на целевом источнике данных (например, в базе данных через Entity Framework).

Почему это важно? В отличие от IEnumerable, методы IQueryable (такие как Where, OrderBy, Select) не выполняют операцию сразу. Вместо этого они модифицируют дерево выражений. Запрос выполняется только при материализации — вызове метода, который приводит к перечислению результатов (например, ToList(), ToArray(), FirstOrDefault(), Count()).

Пример:

// DbContext использует IQueryable
IQueryable<User> query = dbContext.Users
    .Where(u => u.Age > 18)          // Добавляет условие в Expression
    .OrderBy(u => u.LastName)        // Добавляет сортировку в Expression
    .Select(u => new { u.Id, u.Name }); // Добавляет проекцию в Expression

// Запрос НЕ выполнен. Он представлен в виде Expression и Provider.
Console.WriteLine(query.Expression); // Можно посмотреть сгенерированное выражение

// МАТЕРИАЛИЗАЦИЯ: запрос транслируется в SQL и выполняется на сервере БД.
var adultUsers = query.ToList();
Ключевое отличие от IEnumerable: Аспект IEnumerable<T> IQueryable<T>
Место выполнения В памяти приложения (LINQ to Objects). На стороне провайдера данных (например, SQL-сервер).
Фильтрация Where(u => u.Age > 18) загружает ВСЕ данные, затем фильтрует в памяти. Where(u => u.Age > 18) добавляет условие WHERE Age > 18 в SQL-запрос.
Оптимизация Меньше возможностей для оптимизации. Провайдер может оптимизировать итоговый запрос (индексы, JOIN).

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