TGViewer
.NET Разработчик .NET Разработчик @netdeveloperdiary · 6.75K subscribers
Post #3166 1.81K
День 2639. #ЗаметкиНаПолях
Первичные Конструкторы для Внедрения Зависимостей. Начало

Когда в C# 12 внедрили первичные конструкторы в классах, это было встречено скептически. Неявное изменяемое поле вместо явного поля только для чтения? Это казалось обменом безопасности на удобство. Но практика показала, что они значительно сокращают количество шаблонного кода в сервисных классах, а ловушка, которой все опасались, становится управляемой, если вы о ней знаете.

Классы сервисов
Вот как раньше выглядели классы сервисов:
public class OrderService
{
private readonly IOrderRepository _repo;
private readonly ILogger<OrderService> _logger;

public OrderService(
IOrderRepository repo,
ILogger<OrderService> logger)
{
_repo = repo;
_logger = logger;
}

public async Task<Order?> GetAsync(Guid id)
{
_logger.LogInformation("Получаем заказ {OrderId}", id);
return await _repo.GetByIdAsync(id);
}
}

И как теперь:
public class OrderService(
IOrderRepository repo,
ILogger<OrderService> logger)
{
public async Task<Order?> GetAsync(Guid id)
{
logger.LogInformation("Получаем заказ {OrderId}", id);
return await repo.GetByIdAsync(id);
}
}

Объявления полей, тело конструктора, присваивания — всё исчезло. Параметры перехватываются и становятся доступны во всём теле класса.

Это наиболее распространённый вариант использования первичных конструкторов: внедрение зависимостей в классы сервисов. Вы объявляете то, что вам нужно, и используете это напрямую. Экономия на шаблонном коде быстро накапливается. Классам сервисов обычно не нужно проверять или преобразовывать свои зависимости. DI-контейнер предоставляет их, а вы их используете. Первичные конструкторы идеально подходят для этого.

Создание сущностей (с оговоркой)
Можно использовать первичные конструкторы для сущностей предметной области и объектов-значений, где необходимо обеспечить обязательность параметров во время создания:
public class Order(Guid customerId, Money total)
{
public Guid Id { get; } = Guid.NewGuid();
public Guid CustomerId { get; } = customerId;
public Money Total { get; } = total;
public OrderStatus Status { get; private set; }
= OrderStatus.Pending;
public DateTime CreatedAt { get; }
= DateTime.UtcNow;

public void Confirm()
{
if (Status != OrderStatus.Pending)
{
throw new InvalidOperationException(
$"Нельзя подтвердить заказ в статусе {Status}.");
}

Status = OrderStatus.Confirmed;
}
}

Создать заказ без customerId и total невозможно. Первичный конструктор делает это ограничение видимым на уровне объявления типа.

Обратите внимание на ключевое отличие от шаблона класса сервиса: параметры первичного конструктора присваиваются свойствам с инициализаторами (= customerId). Это важно, и это приводит к самой большой проблеме первичных конструкторов.

Окончание следует…

Источник:
https://www.milanjovanovic.tech/blog/why-i-switched-to-primary-constructors-for-di-in-csharp
  • 👍 3
More from @netdeveloperdiary
  1. Sep 27, 2026День 2797. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Окончание Начало Продол…
  2. Sep 26, 2026День 2796. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Продолжение Начало Три…
  3. Sep 25, 2026День 2795. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Начало Проблема с позиц…
  4. Sep 24, 2026День 2794. #Оффтоп #Здоровье Сегодня будет необычный пост. Завтра в Москве стартует конфер…
  5. Sep 23, 2026День 2793. #ЗаметкиНаПолях #SQL 10 Редких Возможностей SQL, Которые Стоит Знать Каждому. Ч…
  6. Sep 22, 2026День 2792. #ЗаметкиНаПолях #SQL 10 Редких Возможностей SQL, Которые Стоит Знать Каждому. Ч…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →