Русский
Русский
English
Статистика
Реклама

Избавляемся от постоянного написания конструкторов для инжекта зависимостей с помощью C Source Generators

В апреле 2020-го года разработчиками платформы .NET 5 был анонсированновый способ генерации исходного кода на языке программирования C# с помощью реализации интерфейсаISourceGenerator. Данный способ позволяет разработчикам анализировать пользовательский код исоздавать новые исходные файлына этапе компиляции. При этом, API новых генераторов исходного кода схож с APIанализаторов Roslyn. Генерировать код можно как с помощьюRoslyn Compiler API, так и методом конкатенации обычных строк.

В данном материале рассмотрим библиотеку HarabaSourceGenerators.Generators и то, как она реализована

HarabaSourceGenerators.Generators

Все мы привыкли инжектить кучу зависимостей в класс и инициализировать их в конструкторе. На выходе обычно получаем что-то типа этого

public partial class HomeController : Controller{     private readonly TestService _testService;             private readonly WorkService _workService;             private readonly ExcelService _excelService;             private readonly MrNService _mrNService;             private readonly DotNetTalksService _dotNetTalksService;            private readonly ILogger<HomeController> _logger;     public HomeController(         TestService testService,         WorkService workService,         ExcelService excelService,         MrNService mrNService,         DotNetTalksService dotNetTalksService,         ILogger<HomeController> logger)     {         _testService = testService;         _workService = workService;         _excelService = excelService;         _mrNService = mrNService;         _dotNetTalksService = dotNetTalksService;         _logger = logger;     }}

Пора с этим кончать!

Представляю вашему вниманию новый, удобный и элегантный способ:

public partial class HomeController : Controller{    [Inject]    private readonly TestService _testService;            [Inject]    private readonly WorkService _workService;            [Inject]    private readonly ExcelService _excelService;            [Inject]    private readonly MrNService _mrNService;            [Inject]    private readonly DotNetTalksService _dotNetTalksService;            [Inject]    private readonly ILogger<HomeController> _logger; }

А что, если лень указывать для каждой зависимости атрибут Inject?

Не проблема, можно указать атрибут Inject для всего класса. В таком случае будут браться все приватные поля с модификатором readonly:

[Inject]public partial class HomeController : Controller{    private readonly TestService _testService;            private readonly WorkService _workService;            private readonly ExcelService _excelService;            private readonly MrNService _mrNService;            private readonly DotNetTalksService _dotNetTalksService;            private readonly ILogger<HomeController> _logger;}

Отлично. Но что, если есть поле, которое нужно не для инжекта?

Указываем для такого поля атрибут InjectIgnore:

[Inject]public partial class HomeController : Controller{    [InjectIgnore]    private readonly TestService _testService;            private readonly WorkService _workService;            private readonly ExcelService _excelService;            private readonly MrNService _mrNService;            private readonly DotNetTalksService _dotNetTalksService;            private readonly ILogger<HomeController> _logger;}

Ну окей, а что, если я хочу указать последовательность для зависимостей?

Угадайте что? Правильно, не проблема. Есть два способа:

1) Расставить поля в нужной последовательности в самом классе.
2) В атрибут Inject передать порядковый номер зависимости

public partial class HomeController : Controller{    [Inject(2)]    private readonly TestService _testService;    [Inject(1)]    private readonly WorkService _workService;    [Inject(3)]    private readonly ExcelService _excelService;    [Inject(4)]    private readonly MrNService _mrNService;    [Inject(5)]    private readonly DotNetTalksService _dotNetTalksService;    [Inject(6)]    private readonly ILogger<HomeController> _logger;}

Как видим, последовательность успешно сохранена.

Взглянем на реализацию

У нас есть класс InjectSourceGenerator, который реализует интерфейс ISourceGenerator.
Мы пробегаемся по синтаксическому дереву. Получаем семантическую модель, а так же все классы, которые имеют атрибут Inject. После чего генерируем для каждого такого класса - новый partial класс, в который мы помещаем конструктор.
Сгенерированный файл "{className}.Constructor.cs" мы помещаем в контекст выполнения

public void Execute(GeneratorExecutionContext context){var compilation = context.Compilation;var attributeName = nameof(InjectAttribute).Replace("Attribute", string.Empty);foreach (var syntaxTree in compilation.SyntaxTrees){var semanticModel = compilation.GetSemanticModel(syntaxTree);var targetTypes = syntaxTree.GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>().Where(x => x.ContainsClassAttribute(attributeName) || x.ContainsFieldAttribute(attributeName)).Select(x => semanticModel.GetDeclaredSymbol(x)).OfType<ITypeSymbol>();foreach (var targetType in targetTypes){string source = GenerateInjects(targetType);context.AddSource($"{targetType.Name}.Constructor.cs", SourceText.From(source, Encoding.UTF8));}}}

А вот собственно и сама генерация класса. Вы, наверное, удивлены. Но еще в начале я упомянул, что генерировать код можно, написав это все чудо обычными строками.

private string GenerateInjects(ITypeSymbol targetType){            return $@" using System;namespace {targetType.ContainingNamespace}{{    public partial class {targetType.Name}    {{        {GenerateConstructor(targetType)}    }}}}";}

Давайте взглянем на метод генерации самого конструктора (самая важная часть кода).
И так, сперва мы получаем поля. Если атрибут Inject указан у класса, то мы берем все поля, которые имеют модификатор readonly и не имеют атрибута InjectIgnore. Иначе мы берем все поля, у которых есть атрибут Inject. Дальше мы выполняем сортировку, чтобы дать возможность пользователям выбирать последовательность параметров. Думаю остальное все понятно

private string GenerateConstructor(ITypeSymbol targetType){var parameters = new StringBuilder();var fieldsInitializing = new StringBuilder();var fields = targetType.GetAttributes().Any(x => x.AttributeClass.Name == nameof(InjectAttribute)) ? targetType.GetMembers().OfType<IFieldSymbol>().Where(x => x.IsReadOnly && !x.GetAttributes().Any(y => y.AttributeClass.Name == nameof(InjectIgnoreAttribute))): targetType.GetMembers().OfType<IFieldSymbol>().Where(x => x.GetAttributes().Any(y => y.AttributeClass.Name == nameof(InjectAttribute)));var orderedFields = fields.OrderBy(x => x.GetAttributes() .First(e => e.AttributeClass.Name == nameof(InjectAttribute)) .ConstructorArguments.FirstOrDefault().Value ?? default(int)).ToList();foreach (var field in orderedFields){var parameterName = field.Name.TrimStart('_');parameters.Append($"{field.Type} {parameterName},");fieldsInitializing.AppendLine($"this.{field.Name} = {parameterName};");}return $@"public {targetType.Name}({parameters.ToString().TrimEnd(',')})  {{  {fieldsInitializing}  }}";}

Минусы

Класс обязательно должен иметь ключевое слово partial, чтобы была возможность создать конструктор в стороннем файле. На мой взгляд, это единственный минус!

Исходный код генераторадоступен наGitHub.

Источник: habr.com
К списку статей
Опубликовано: 28.03.2021 20:06:28
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Open source

Net

C

Sourcegenerator

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru