Hilangkan kerumitan menulis konstruktor untuk injeksi ketergantungan dengan Generator Sumber C #

Pada April 2020, pengembang platform .NET 5   mengumumkan  cara baru untuk menghasilkan kode sumber dalam bahasa pemrograman C # - menggunakan implementasi antarmuka  ISourceGenerator



. Metode ini memungkinkan pengembang untuk menganalisis kode khusus dan  membuat file sumber baru  pada waktu kompilasi. Pada saat yang sama, API generator kode sumber baru mirip dengan API  penganalisis Roslyn . Anda dapat menghasilkan kode baik menggunakan  Roslyn Compiler API dan dengan menggabungkan string biasa.





Pada artikel ini, kami akan mempertimbangkan pustaka HarabaSourceGenerators.Generators dan bagaimana penerapannya





HarabaSourceGenerators.Generators

Kita semua terbiasa memasukkan sekumpulan dependensi ke dalam kelas dan menginisialisasinya di konstruktor. Outputnya biasanya seperti ini





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;
     }
}
      
      



Saatnya mengakhirinya!





Saya persembahkan untuk perhatian Anda cara baru, nyaman dan elegan:





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;
 }
      
      



Tetapi bagaimana jika Anda terlalu malas untuk menentukan atribut Inject untuk setiap dependensi?





Tidak masalah, Anda bisa menentukan atribut Inject untuk seluruh kelas. Dalam kasus ini, semua bidang pribadi dengan pengubah hanya-baca akan diambil:





[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;
}
      
      



Luar biasa. Tetapi bagaimana jika ada bidang yang tidak dibutuhkan untuk injeksi?





Kami menetapkan atribut InjectIgnore untuk bidang seperti itu:





[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;
}
      
      



Oke, jadi bagaimana jika saya ingin mengurutkan dependensi?





Tebak apa? Benar, tidak masalah. Ada dua cara:





1) Atur bidang dalam urutan yang diinginkan di kelas itu sendiri.

2) Berikan nomor seri ketergantungan tersebut ke atribut 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;
}
      
      



Seperti yang Anda lihat, urutannya telah berhasil disimpan.





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.

Nuget HarabaSourceGenerators








All Articles