Введение
В современной экосистеме C# язык интегрированных запросов LINQ давно перестал быть просто удобным инструментом для работы с коллекциями — это фундаментальный парадигмальный сдвиг в мышлении разработчика. Представленный еще в 2008 году вместе с .NET Framework 3.5, LINQ кардинально изменил подход к обработке данных, унифицировав работу с массивами, коллекциями, XML, базами данных и даже удаленными источниками через единый декларативный синтаксис.
Базовый синтаксис: Method vs Query
1.1. Метод синтаксиса (Method Syntax)
var filteredUsers = users
.Where(u => u.Age > 18)
.OrderBy(u => u.LastName)
.ThenBy(u => u.FirstName)
.Select(u => new { u.FullName, u.Email })
.ToList();
1.2. Синтаксис запросов (Query Syntax)
var filteredUsers =
from u in users
where u.Age > 18
orderby u.LastName, u.FirstName
select new { u.FullName, u.Email };
Фильтрация и проекция
2.1. Комбинированные условия Where
var activeAdults = products
.Where(p => p.Price > 1000 && p.CategoryId == 5 || p.IsFeatured)
.Where(p => !p.IsDeleted)
.ToList();
2.2. Select с преобразованием
var productDtos = products
.Select(p => new ProductDto
{
Id = p.Id,
Name = p.Name.ToUpper(),
PriceWithTax = p.Price * 1.2m,
IsExpensive = p.Price > 10000
})
.ToList();
Группировка и агрегация
3.1. Группировка по нескольким полям
var salesByCategoryAndYear = orders
.GroupBy(o => new { o.Category, Year = o.OrderDate.Year })
.Select(g => new
{
g.Key.Category,
g.Key.Year,
Total = g.Sum(o => o.Amount),
Count = g.Count(),
Average = g.Average(o => o.Amount)
})
.OrderByDescending(x => x.Total)
.ToList();
3.2. Агрегатные функции
var stats = products.Aggregate(new
{
Min = decimal.MaxValue,
Max = decimal.MinValue,
Total = 0m
},
(acc, product) => new
{
Min = product.Price < acc.Min ? product.Price : acc.Min,
Max = product.Price > acc.Max ? product.Price : acc.Max,
Total = acc.Total + product.Price
});
Соединения (Joins)
4.1. Inner Join
var userOrders = users
.Join(orders,
user => user.Id,
order => order.UserId,
(user, order) => new { user.Name, order.Amount, order.Date })
.ToList();
4.2. GroupJoin (эквивалент LEFT JOIN)
var usersWithOrders = users
.GroupJoin(orders,
user => user.Id,
order => order.UserId,
(user, userOrders) => new
{
User = user,
OrderCount = userOrders.Count(),
TotalAmount = userOrders.Sum(o => o.Amount)
})
.ToList();
Работа с коллекциями
5.1. Distinct по свойству
var uniqueCategories = products
.Select(p => p.Category)
.Distinct()
.ToList();
var uniqueUsers = users
.DistinctBy(u => u.Email)
.ToList();
5.2. Разбиение коллекций
var page = users.Skip(10).Take(20).ToList();
var batches = products.Chunk(100);
var (expensive, cheap) = products
.Partition(p => p.Price > 1000);
Деferred vs Immediate Execution
6.1. Отложенное выполнение (Deferred)
var query = users.Where(u => u.IsActive);
foreach (var user in query) { }
var activeUsers = query.ToList();
var count = query.Count();
6.2. Немедленное выполнение (Immediate)
.ToList() .ToArray() .ToDictionary()
.Count() .Sum() .Average() .First() .Single()
Оптимизация производительности
7.1. Избегание множественных итераций
var count = users.Where(u => u.Age > 18).Count();
var adults = users.Where(u => u.Age > 18).ToList();
var adultList = users.Where(u => u.Age > 18).ToList();
var count = adultList.Count;
7.2. Индексы в Where
var filtered = users
.Where((u, index) => u.IsActive && index % 2 == 0)
.ToList();
LINQ to Entities (Entity Framework)
8.1. Фильтрация на стороне БД
var users = await context.Users
.Where(u => u.Age > 18 && u.Name.StartsWith("A"))
.OrderBy(u => u.RegistrationDate)
.Select(u => new { u.Id, u.Name })
.ToListAsync();
8.2. Жадная загрузка (Eager Loading) с фильтрацией
var orders = await context.Orders
.Include(o => o.Items.Where(i => i.Price > 100))
.ThenInclude(i => i.Product)
.Where(o => o.Date > DateTime.UtcNow.AddDays(-30))
.ToListAsync();
Кастомные операторы и расширения
9.1. Собственный метод-расширения
public static IEnumerable<User> ActiveUsers(this IEnumerable<User> users)
{
return users.Where(u => u.IsActive && !u.IsDeleted);
}
var active = users.ActiveUsers().ToList();
9.2. Batch-обработка с кастомным агрегатором
public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> source, int size)
{
var batch = new List<T>(size);
foreach (var item in source)
{
batch.Add(item);
if (batch.Count == size)
{
yield return batch;
batch = new List<T>(size);
}
}
if (batch.Count > 0) yield return batch;
}
foreach (var batch in products.Batch(100))
{
await ProcessBatchAsync(batch);
}
Обработка исключений и null
10.1. Безопасный доступ к свойствам
var validPrices = products
.Select(p => p.Price)
.Where(price => price.HasValue)
.Select(price => price!.Value)
.ToList();
10.2. DefaultIfEmpty для обработки отсутствующих данных
var lastOrder = user.Orders
.Where(o => o.Status == OrderStatus.Completed)
.OrderByDescending(o => o.Date)
.FirstOrDefault()
?? Order.CreateEmpty();
Сложные запросы с вложенными коллекциями
var departmentReport = departments
.Select(d => new
{
Department = d.Name,
Employees = d.Employees
.Where(e => e.HireDate.Year >= 2020)
.GroupBy(e => e.Position)
.Select(g => new
{
Position = g.Key,
Count = g.Count(),
AvgSalary = g.Average(e => e.Salary)
})
.OrderByDescending(x => x.AvgSalary)
.ToList()
})
.Where(d => d.Employees.Any())
.OrderBy(d => d.Department)
.ToList();
Заключение
LINQ в C# — это гораздо больше, чем просто удобный способ фильтрации списков. Это целостная философия работы с данными, которая воспитывает у разработчика декларативный стиль мышления и глубокое понимание операций преобразования информации.