Введение:
С выходом C# 9 и последующих версий произошла тихая революция в языке, которая кардинально изменила подход к работе с данными и логикой. Две ключевые возможности — records и pattern matching — превратили C# из просто объектно-ориентированного языка в язык, где функциональное программирование стало first-class citizen.
Почему эти функции изменили правила игры?
-
Records устраняют до 80% шаблонного кода для data-классов
-
Pattern matching делает сложные условия читаемыми и поддерживаемыми
-
Иммутабельность по умолчанию предотвращает целый класс ошибок
-
Выразительность кода приближается к F# при сохранении мощи .NET
Что вы получите, освоив эти инструменты?
-
Код, который выглядит как описание бизнес-логики, а не технических деталей
-
В 3 раза меньше строк кода в моделях и сервисах
-
Автоматическую проверку на полноту обработки всех случаев
-
Производительность на уровне низкоуровневых оптимизаций
1. Records: неизменяемые данные с минимальным кодом
1.1. Базовый record
public record Person(string FirstName, string LastName);
1.2. Использование
var person = new Person(«John», «Doe»);
var (firstName, lastName) = person;
1.3. Наследование records
public record Employee(string FirstName, string LastName, string Department)
: Person(FirstName, LastName);
2. Pattern Matching: элегантные условия
2.1. Простой pattern matching
public string GetDescription(object obj) => obj switch
{
Person p => $»Person: {p.FirstName} {p.LastName}»,
Employee e => $»Employee: {e.Department}»,
_ => «Unknown»
};
2.2. Property patterns
public string CheckEmployee(Employee emp) => emp switch
{
{ Department: «IT», FirstName: «John» } => «IT John»,
{ Department: «HR» } => «HR Employee»,
_ => «Other»
};
2.3. Рекурсивные patterns
public decimal CalculateTax(Income income) => income switch
{
{ Amount: < 1000 } => 0,
{ Amount: >= 1000 and < 5000 } => income.Amount * 0.1m,
{ Amount: >= 5000 } => income.Amount * 0.2m
};
3. Комбинация records и pattern matching
3.1. Deconstruction в patterns
public string ProcessPerson(Person person) => person switch
{
(«John», «Doe») => «Known person»,
(var first, var last) => $»New person: {first} {last}»
};
3.2. Сложные сценарии
public record Order(Product Product, int Quantity);
public record Product(string Name, decimal Price);public decimal CalculateDiscount(Order order) => order switch
{
{ Product: { Price: > 100 }, Quantity: > 5 } => 0.2m,
{ Product.Name: «Premium» } => 0.15m,
_ => 0.1m
};
4. Продвинутые возможности
4.1. Positional records с методами
public record Vector(double X, double Y)
{
public double Magnitude => Math.Sqrt(X * X + Y * Y);
public Vector Normalize() => this with { X = X / Magnitude, Y = Y / Magnitude };
}
4.2. Recursive patterns
public bool IsValidNode(TreeNode node) => node switch
{
{ Value: null } => false,
{ Left: null, Right: null } => true,
{ Left: var left, Right: var right } => IsValidNode(left) && IsValidNode(right)
};
5. Производительность и оптимизация
5.1. Records vs классы
// Record
public record Point(int X, int Y);// Класс с аналогичной функциональностью
public class PointClass
{
public int X { get; }
public int Y { get; }public PointClass(int x, int y) => (X, Y) = (x, y);
public override bool Equals(object obj) => /* шаблонный код */;
public override int GetHashCode() => /* шаблонный код */;
public void Deconstruct(out int x, out int y) => (x, y) = (X, Y);
}
5.2. Компиляторная оптимизация
public bool IsSpecialPoint(Point point) => point is (0, 0) or (1, 1) or (-1, -1);
6. Практические примеры использования в реальных проектах
6.1. Обработка API-ответов с pattern matching
public async Task<ActionResult> HandleResponse(HttpResponseMessage response)
{
return response switch
{
{ StatusCode: HttpStatusCode.OK } content =>
Ok(await content.Content.ReadFromJsonAsync<Order>()),
{ StatusCode: HttpStatusCode.NotFound } => NotFound(),
{ StatusCode: HttpStatusCode.Unauthorized } => Unauthorized(),
_ => StatusCode((int)response.StatusCode)
};
}
6.2. Модели данных с records для DTO
public record ApiResponse<T>(T Data, bool Success, string ErrorMessage = «»);
public record UserDto(Guid Id, string Email, DateTime CreatedAt);
public record CreateUserRequest(string Email, string Password);
6.3. Валидация входящих запросов
public ValidationResult ValidateRequest(CreateUserRequest request) => request switch
{
{ Email: null or «» } => ValidationResult.Invalid(«Email required»),
{ Password: null or «» } => ValidationResult.Invalid(«Password required»),
{ Password.Length: < 8 } => ValidationResult.Invalid(«Password too short»),
{ Email: var email } when !email.Contains(‘@’) => ValidationResult.Invalid(«Invalid email»),
_ => ValidationResult.Valid()
};
7. Работа с коллекциями и LINQ
7.1. Фильтрация с property patterns
var highValueOrders = orders
.Where(order => order is { Amount: > 1000, Status: OrderStatus.Completed })
.ToList();
7.2. Преобразование данных
var userSummaries = users
.Select(user => user switch
{
{ Age: >= 18 } adult => new AdultSummary(adult.Name, adult.Income),
{ Age: < 18 } minor => new MinorSummary(minor.Name, minor.Grade),
_ => throw new InvalidOperationException()
});
8. Обработка ошибок и исключения
8.1. Pattern matching в блоках catch
try
{
await ProcessOrderAsync(order);
}
catch (Exception ex) when (ex is ApiException { StatusCode: 404 })
{
return HandleNotFound();
}
catch (ApiException { StatusCode: 500 } apiEx)
{
return HandleServerError(apiEx);
}
8.2. Result pattern для обработки ошибок
public Result<Order, Error> ProcessOrder(Order order) => order switch
{
{ Items: [] } => Error.EmptyOrder,
{ Customer: { IsBlocked: true } } => Error.BlockedCustomer,
{ Total: > 10000 } => Error.AmountTooLarge,
_ => order with { Status = OrderStatus.Processing }
};
9. Оптимизация производительности
9.1. Использование sealed records
public sealed record PaymentResult(bool Success, string TransactionId);
9.2. Struct records для value semantics
public readonly record struct Point3d(double X, double Y, double Z);
9.3. Компиляторная оптимизация switch expressions
public decimal CalculateShipping(Order order) => order switch
{
{ Weight: <= 1 } => 5.0m,
{ Weight: <= 5 } => 10.0m,
{ Weight: <= 10 } => 15.0m,
{ Weight: > 10 } => 20.0m
};
10. Интеграция с другими технологиями
10.1. Entity Framework Core с records
public record OrderDetail(Guid OrderId, string ProductName, int Quantity);
modelBuilder.Entity<Order>()
.OwnsMany(o => o.Details, d =>
{
d.ToJson();
});
10.2. Serialization с System.Text.Json
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true
};var json = JsonSerializer.Serialize(user, options);
var user = JsonSerializer.Deserialize<User>(json, options);
Заключение
Records и pattern matching — это не просто новые синтаксические возможности C#, а фундаментальный сдвиг в парадигме разработки. Они демонстрируют эволюцию языка в сторону функционального программирования, сохраняя при этом все преимущества объектно-ориентированного подхода.
Ключевые преимущества, которые мы рассмотрели:
-
Выразительность — код становится более читаемым и лаконичным
-
Безопасность — иммутабельность и exhaustive matching предотвращают целые классы ошибок
-
Производительность — встроенные оптимизации компилятора работают на вас
-
Поддержка — полная интеграция с экосистемой .NET и популярными библиотеками
Эти инструменты уже стали стандартом де-факто в современных .NET проектах и их освоение — обязательное требование для профессионального разработчика в 2025 году.
Для дальнейшего изучения
-
Property Patterns в C# 10 — углубленное изучение рекурсивных паттернов
-
Record Structs — особенности работы с value-type records
-
Source Generators для Records — автоматическая генерация кода
-
Pattern Matching в LINQ — комбинация с запросами
-
Оптимизация производительности — бенчмаркинг различных подходов
-
Миграция с классов на Records — лучшие практики рефакторинга
-
F# Influence — изучение корней этих возможностей для deeper understanding

