← Назад к вопросам

Как работает метод Where?

2.0 Middle🔥 151 комментариев
#Основы C# и .NET

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как работает метод Where в C# (LINQ)

Метод Where — это один из фундаментальных методов LINQ (Language Integrated Query), предназначенный для фильтрации последовательностей элементов на основе заданного предиката (условия).

Основной принцип работы

Метод Where принимает два параметра:

  1. Исходную коллекцию (реализующую IEnumerable<T> или IQueryable<T>)
  2. Предикат — функцию, принимающую элемент типа T и возвращающую bool

Результат — новая последовательность, содержащая только те элементы, для которых предикат вернул true.

// Пример базового использования
var numbers = new List<int> { 1, pane2, 3, 4, 5, 6 };
var evenNumbers = numbers.Where(n => n % 2 == 0);

// Результат: [2, 4, 6]

Две формы реализации: для IEnumerable и IQueryable

1. Для IEnumerable<T> (отложенное выполнение в памяти)

Метод расширения для IEnumerable<T> реализует отложенное выполнение (deferred execution):

public static IEnumerable<TSource> Where<TSource>(
    this IEnumerable<TSource> source, 
    Func<TSource, bool> predicate)
{
    foreach (TSource element in source)
    {
        if (predicate(element))
        {
            yield return element;
        }
    }
}

Ключевые особенности:

  • Использует итераторы (yield return) для ленивой оценки
  • Фильтрация происходит только при итерации по результату
  • Выполняется на стороне клиента (в памяти приложения)
  • Поддерживает цепочку методов LINQ
// Пример отложенного выполнения
var query = numbers.Where(n => n > 2); // Запрос еще не выполнен!
Console.WriteLine("Запрос создан");

foreach (var num in query) // Выполнение происходит здесь
{
    Console.WriteLine(num);
}

2. Для IQueryable<T> (преобразование в выражение дерева)

Для IQueryable<T> используется другая перегрузка с Expression<Func<T, bool>>:

public static IQueryable<TSource> Where<TSource>(
    this IQueryable<TSource> source, 
    Expression<Func<TSource, bool>> predicate)

Ключевые различия:

  • Предикат передается как дерево выражений (expression tree), а не как делегат
  • Фильтрация может быть переведена в SQL (или другой язык запросов)
  • Выполняется на стороне сервера (базы данных, удаленного источника)
  • Оптимизирует производительность за счет минимизации передаваемых данных
// Пример с Entity Framework
using (var context = new AppDbContext())
{
    // Предикат преобразуется в SQL WHERE clause
    var users = context.Users
        .Where(u => u.Age > 18 && u.IsActive)
        .ToList(); // SQL выполняется здесь
}

Важные аспекты работы метода Where

Отложенное выполнение и материализация

var list = new List<int> { 1, 2, 3, 4 };
var filtered = list.Where(x => x > 2); // Только создается запрос

// Материализация происходит одним из способов:
var materialized1 = filtered.ToList();    // 1. ToList()
var materialized2 = filtered.ToArray();   // 2. ToArray()
var materialized3 = filtered.Count();     // 3. Агрегатные методы

Множественные условия и композиция

// Комбинирование условий
var complexFilter = products
    .Where(p => p.Price > 100)
    .Where(p => p.Category == "Electronics")
    .Where(p => p.StockQuantity > 0);

// Эквивалентно:
var sameFilter = products
    .Where(p => p.Price > 100 && p.Category == "Electronics" && p.StockQuantity >*,* 0);

Индексация в перегрузках

// Перегрузка с индексом элемента
var indexedFilter = items.Where((item, index) => item.Length > index);

// Использование:
var strings = new[] { "a", "ab", "abc", "abcd" };
var result = strings.Where((s, idx) => s.Length > idx);
// Результат: ["ab", "abc", "abcd"] (индексы: 1, 2, 3)

Производительность и лучшие практики

  1. Избегайте множественных итераций по одному запросу:

    var query = data.Where(x => ExpensiveCondition(x));
    var count = query.Count(); // Первая итерация
    var list = query.ToList(); // Вторая итерация - условие вычисляется снова!
    
    // Лучше:
    var materialized = query.ToList();
    var count = materialized.Count;
    
  2. Используйте IQueryable для работы с базами данных:

    // Плохо: фильтрация в памяти
    var allUsers = db.Users.ToList();
    var activeUsers = allUsers.Where(u => u.IsActive); // Фильтрация в памяти
    
    // Хорошо: фильтрация в SQL
    var activeUsers = db.Users.Where(u => u.IsActive).ToList();
    
  3. Оптимизация сложных условий:

    // Помещайте более дешевые условия первыми
    var optimized = data
        .Where(x => x.IsActive)          // Дешевая проверка bool
        .Where(x => x.Name.Contains("a")) // Более дорогая операция
        .Where(x => ExpensiveMethod(x));  // Самая дорогая операция
    

Внутренняя оптимизация в .NET

Современные версии .NET применяют различные оптимизации:

  • Векторизация операций для примитивных типов
  • Инлайн методов для простых предикатов
  • Кэширование делегатов для повторяющихся условий
  • Специальные реализации для массивов и списков

Пример реального использования

public class OrderService
{
    public IEnumerable<Order> GetActiveOrdersForCustomer(int customerId)
    {
        return _orderRepository.GetAll()
            .Where(order => order.CustomerId == customerId)
            .Where(order => order.Status != OrderStatus.Cancelled)
            .Where(order => order.TotalAmount > 0)
            .OrderByDescending(order => order.OrderDate);
    }
    
    public IQueryable<Order> GetLargeOrdersFromDatabase()
    {
        return _dbContext.Orders
            .Where(o => o.TotalAmount > 10000)
            .Where(o => o.OrderDate.Year == DateTime.Now.Year);
    }
}

Метод Where является краеугольным камнем LINQ, обеспечивая декларативный, выразительный и эффективный способ фильтрации данных как в памяти, так и в различных источниках данных. Его правильное понимание и использование критически важно для написания чистого, производительного и поддерживаемого C# кода.