В чем разница между IEnumerable и IQueryable в C#?

Ответ

Разница заключается в месте выполнения запроса и, как следствие, в его эффективности.

Характеристика IEnumerable<T> IQueryable<T>
Пространство имен System.Collections System.Linq
Цель Работа с коллекциями в памяти (LINQ to Objects). Работа с внешними источниками данных (LINQ to Entities, LINQ to SQL).
Место выполнения Запрос выполняется на стороне клиента (в памяти приложения). Запрос транслируется в язык источника (например, SQL) и выполняется на стороне сервера (БД).
Философия «Принеси все данные, потом отфильтруй». «Расскажи, что нужно, я сам отфильтрую и принесу результат».

Наглядный пример с Entity Framework Core:

// Контекст базы данных
var dbContext = new AppDbContext();

// ПЛОХО: Использование IEnumerable
IEnumerable<Product> productsEnum = dbContext.Products; // Неявное приведение
var cheapProductsEnum = productsEnum.Where(p => p.Price < 50).ToList();
// Что происходит:
// 1. `dbContext.Products` выполняется как SQL: SELECT * FROM Products
// 2. ВСЕ продукты загружаются в память приложения.
// 3. Фильтр `Where(p => p.Price < 50)` применяется в памяти к уже загруженным данным.

// ХОРОШО: Использование IQueryable
IQueryable<Product> productsQuery = dbContext.Products;
var cheapProductsQuery = productsQuery.Where(p => p.Price < 50).ToList();
// Что происходит:
// 1. Запрос НЕ выполняется сразу. Строится выражение.
// 2. При вызове `.ToList()` выражение транслируется в SQL:
//    SELECT * FROM Products WHERE Price < 50
// 3. В память загружаются ТОЛЬКО отфильтрованные данные.

Ключевые выводы:

  • Используйте IQueryable, когда работаете с ORM (EF Core) и хотите, чтобы фильтрация, сортировка и пагинация выполнялись на стороне БД. Это критически важно для производительности.
  • Используйте IEnumerable, когда источник данных уже находится в памяти (массив, список) или после того, как вы материализовали IQueryable (вызвали .ToList(), .ToArray()).
  • Материализация: Вызов методов, возвращающих коллекции (.ToList(), .First()), или перечисление через foreach приводит к выполнению запроса и переходу от IQueryable к IEnumerable.

Ответ 18+ 🔞

Давай разберёмся, в чём тут собака зарыта, а то народ путает эти две штуки как будто они близнецы-братья. А по факту — разница, как между молотком и микроскопом. Оба вроде инструменты, но если гвозди микроскопом забивать, будет весело, но дохуя неэффективно.

Короче, вся соль в том, где твой запрос выполняется. Это главный камень преткновения.

Признак IEnumerable<T> IQueryable<T>
Откуда ноги растут System.Collections System.Linq
Для чего создан Ковыряться в том, что уже в оперативке лежит (LINQ to Objects). Общаться с внешними источниками типа баз данных (LINQ to SQL, EF Core).
Где работает Всё жрёт ресурсы твоего сервера/компа. Фильтрует то, что уже скачал. Умный, переводит твои хотелки на язык базы (SQL) и заставляет базу данных пахать.
Философия «Дай мне всё, а уж я тут сам разберусь, что мне нужно». Ленивый, но туповатый работник. «Скажи, что тебе надо, я сам на месте всё сделаю и принесу только результат». Смышлёный удалёнщик.

Смотри, как это выглядит в жизни с Entity Framework Core:

// Подключаемся к базе
var db = new AppDbContext();

// ПИЗДЕЦ КАК НЕ НАДО: Используем IEnumerable
IEnumerable<Product> productsEnum = db.Products; // Тут уже подвох!
var cheapShitEnum = productsEnum.Where(p => p.Price < 50).ToList();
// Что происходит по шагам:
// 1. Выполняется тупой SQL: SELECT * FROM Products. ВСЕ строки из таблицы.
// 2. Эти гигабайты данных тащатся по сети и впихиваются в память твоего приложения.
// 3. И только потом, в памяти, начинает работать LINQ to Objects и отфильтровывает то, что Price < 50.
// Итог: сервер ебётся, сеть стонет, оперативка плачет. А ты получил 10 записей из миллиона.

// О, А ВОТ КАК НАДО: Используем IQueryable
IQueryable<Product> productsQuery = db.Products; // Пока это просто "намерение", запрос не выполнен
var cheapShitQuery = productsQuery.Where(p => p.Price < 50).ToList();
// Магия здесь:
// 1. Пока ничего не происходит. Просто строится "дерево выражений".
// 2. В момент вызова `.ToList()` эта умная хуйня трансформируется в идеальный SQL:
//    SELECT * FROM Products WHERE Price < 50
// 3. База данных, которая для этого и создана, моментально находит нужные строки.
// 4. По сети летит только результат — те самые 10 записей.
// Итог: все довольны, особенно твой продовый сервер.

Так что запомни раз и навсегда:

  • Тащи IQueryable, когда работаешь с базой через ORM (EF Core/Dapper). Пусть вся тяжкая работа по сортировке, фильтрации и пагинации делается там, где для этого тысячи оптимизаций — на стороне СУБД. Производительность будет в ахуе.
  • Хватай IEnumerable, когда данные уже болтаются в памяти (обычный List, массив) или после того, как ты материализовал IQueryable (вызвал .ToList(), .ToArray(), .First()).
  • Материализация — это точка невозврата. Как только вызвал .ToList() от IQueryable — всё, поезд ушёл. Дальше работаешь с тем, что скачал, в памяти. Любые последующие .Where() будут мучать уже твой сервер, а не базу.

Не делай из базы данных почтальона, который тащит тебе весь каталог «Озона», чтобы ты выбрал одну книжку. Скажи ему номер книги, и пусть принесёт только её.