В чем различия между отложенным и немедленным выполнением запросов в LINQ?

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

Ответ

Отложенное (ленивое) выполнение означает, что запрос не выполняется в момент его объявления. Вычисление откладывается до момента фактического перечисления результатов (итерирования). Немедленное выполнение приводит к немедленному вычислению и материализации результата.

Пример, демонстрирующий разницу:

// 1. СОЗДАНИЕ ЗАПРОСА (Отложенное выполнение)
var numbers = new List<int> { 1, 2, 3, 4, 5 };
var deferredQuery = numbers.Where(n => n % 2 == 0); // Запрос ОПРЕДЕЛЕН, но НЕ ВЫПОЛНЕН

numbers.Add(6); // Изменяем исходную коллекцию

// 2. ВЫПОЛНЕНИЕ ЗАПРОСА (происходит здесь)
foreach (var num in deferredQuery) // Запрос выполняется в момент итерации
{
    Console.Write(num + " "); // Вывод: 2 4 6
}
// Запрос "увидел" добавленную 6, потому что выполнился после изменения.

// 3. НЕМЕДЛЕННОЕ ВЫПОЛНЕНИЕ
var immediateResult = numbers.Where(n => n > 3).ToList(); // Запрос выполняется и материализуется в список СРАЗУ
// immediateResult теперь содержит [4, 5, 6] и больше не зависит от изменений в numbers.

Ключевые различия и практические следствия:

Критерий Отложенное выполнение Немедленное выполнение
Время выполнения При перечислении (foreach, ToList). В точке вызова метода материализации.
Повторное выполнение Запрос выполняется заново при каждом перечислении. Результат вычислен один раз и сохранен.
Актуальность данных Использует самые свежие данные на момент перечисления. Фиксирует "снимок" данных на момент вызова.
Типичные операторы Where, Select, OrderBy (возвращают IEnumerable<T>/IQueryable<T>). ToList, ToArray, Count, First, Any (возвращают конкретное значение или коллекцию).

Зачем это нужно? Отложенное выполнение позволяет строить сложные, композируемые запросы поэтапно и выполняет их оптимально одним разом, что особенно важно для IQueryable<T> и запросов к базе данных.