CodeGym /Kurslar /C# SELF /Giriş Source Generators

Giriş Source Generators -ə

C# SELF
Səviyyə, Dərs
Mövcuddur

1. Giriş

Kod yazanda bəzən özünüzə deyirsiniz: «Niyə mən eyni şablon kodu müxtəlif klasslar üçün yenidən və yenidən kopyalayiram?» Yaxud: «Niyə serialization, logging, data mapping — bu qədər oxşar sətirlərdən ibarətdir?» Bəzən istəyirsən ki, kimsə (və ya nəsə) bu zəhmətli şablonu sənin üçün yazsın.

Burada səhnəyə çıxır Source Generators — C#-də .NET 5-dən etibarən olan və hələ də inkişaf edən yeni imkan. Source Generator — bu, compilasiya mərhələsində işləyən və layihənizə dinamik olaraq C# kodu yarada bilən bir kitabxanadır; bu kod final build-dən əvvəl avtomatik şəkildə layihəyə daxil edilir.

Niyə buna ehtiyac var?

  • Rutinin avtomatlaşdırılması: Eyni tipdə klass/metod yazmaqdan xilas edir (boilerplate).
  • Compiler tərəfindən yoxlanılan təhlükəsizlik: Generated kod sizin kodla birlikdə kompilyasiya olunur (T4 və ya reflection-dan fərqli olaraq).
  • Yüksək performans: Serialization, DI, mapping və s. runtime-da reflection xərcləri olmadan.
  • Müasir patternlərin dəstəyi: Kod generasiyası olmadan çətin və ya bahalı olan yanaşmaların həyata keçirilməsi.

Source Generators "qapağın altında" necə işləyir?

Source Generator .NET kitabxanasıdır (adətən Class Library tipli layihə) və ISourceGenerator interfeysini həyata keçirir. Compilyasiya zamanı Roslyn bütün qoşulmuş generatorları işə salır və onlara kodunuzun sintaksis ağacına (syntax tree) çıxış verir.

Generator kodunuzu analiz edir, nəyi harada yaratmaq lazım olduğunu qərarlaşdırır və yeni C# faylları yaradır; kompilyator həmin faylları dərhal kompilyasiya edir.

Avtomatik ToString generasiyası

Gəlin sadə nümunədən başlayaq. Tutaq ki, bizim bir çox property olan klassımız var və bizə ToString implementasiya etmək lazımdır. Əl ilə bu belə görünür:

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public override string ToString()
        => $"Person(Name={Name}, Age={Age})";
}

Amma property-lər çoxalarsa — canın sıxılır və bir şeyi unutmaq da asandır. Source Generator bunu sənin üçün edə bilər!

2. Öz Source Generator-un necə yaratmaq olar?

Layihə yaratmaq

JetBrains Rider və ya Visual Studio açın, Class Library (.NET Standard) tipli yeni layihə yaradın — məhz belə layihələr generator ola bilir. Sonra NuGet paketlərini əlavə edin:

  • Microsoft.CodeAnalysis.CSharp
  • Microsoft.CodeAnalysis.Analyzers

Vacib atributlar

  • [Generator] — bu klassın Source Generator olduğunu göstərir.

Ən sadə generator şablonu

Budur minimal işlək nümunə:

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using System.Text;

[Generator]
public class HelloWorldGenerator : ISourceGenerator
{
    public void Initialize(GeneratorInitializationContext context)
    {
        // Əlavə əməliyyatlar qeydiyyatdan keçirə bilərsiniz (ixtiyari)
    }

    public void Execute(GeneratorExecutionContext context)
    {
        var code = @"
namespace Generated
{
    public static class HelloWorld
    {
        public static string SayHello() => ""Salam, dünya! Mən generasiya olunmuşam!"";
    }
}";
        context.AddSource("HelloWorldGenerator", SourceText.From(code, Encoding.UTF8));
    }
}

Bu sadə generator compilasiya zamanı həmişə HelloWorld statik klassını SayHello metodu ilə əlavə edir.

Source Generators-u əsas tətbiqdə necə istifadə etmək olar?

Generator layihəsini NuGet paketi kimi və ya Project Reference olaraq Analyzer bölməsində qoşun (ətraflı — rəsmi sənədlər).

Generasiya olunmuş kod dərhal layihənizdə istifadə oluna bilər — ayrıca heç nə əlavə etməyə ehtiyac yoxdur, sadəcə istifadə edin:

// Bu avtomatik generasiya olunacaq!
using Generated;

Console.WriteLine(HelloWorld.SayHello());

3. Real nümunə: avtomatik ToString generasiyası

Tutaq ki, biz istəyirik ki, [AutoToString] atributu ilə işarələnmiş bütün klasslar avtomatik olaraq ToString metodunu əldə etsin. Bunun üçün lazım olacaq:

  • Öz atributunu yaratmaq.
  • Bu atributla işarələnmiş bütün klassları analiz etmək.
  • Hər belə klass üçün ToString metodu generasiya etmək.

Atribut

[AttributeUsage(AttributeTargets.Class)]
public class AutoToStringAttribute : Attribute
{
}

Koddaxili istifadə

[AutoToString]
public class Product
{
    public string Name { get; set; }
    public int Price { get; set; }
}

Sadə generasiya loqikası

Generator [AutoToString] olan klassları axtaracaq və təxminən belə kod yaradacaq:

public override string ToString() 
    => $"Product(Name={Name}, Price={Price})";

Generatorun real parçacıqlı kodu

Əsas ideya — Roslyn ilə syntax ağacını gəzməkdir:

public void Execute(GeneratorExecutionContext context)
{
    // Bütün syntax ağaclarını analiz edirik
    foreach (var tree in context.Compilation.SyntaxTrees)
    {
        var root = tree.GetRoot();
        // Lazımi atributla işarələnmiş bütün klassları axtarırıq (sadə nümunə!)
        var classes = root.DescendantNodes()
            .OfType<ClassDeclarationSyntax>()
            .Where(c => c.AttributeLists
                         .SelectMany(al => al.Attributes)
                         .Any(a => a.Name.ToString().Contains("AutoToString")));

        foreach (var @class in classes)
        {
            var className = @class.Identifier.Text;
            // Klassın bütün property-lərini götürürük
            var props = @class.Members
                .OfType<PropertyDeclarationSyntax>()
                .Select(p => p.Identifier.Text)
                .ToArray();

            var toStringCode = string.Join(", ", props.Select(p => $"{p}={{this.{p}}}"));
            var generated = $@"
partial class {className}
{{
    public override string ToString() => $""{className}({toStringCode})"";
}}";

            context.AddSource($"{className}_ToString", SourceText.From(generated, Encoding.UTF8));
        }
    }
}

Qeyd: production səviyyəli kod üçün Roslyn-in SemanticModel vasitəsilə daha düzgün analiz tətbiq olunur.

4. Faydalı nüanslar

Nəyə diqqət etmək lazımdır

Source Generators mövcud source kodu dəyişə bilməz — yalnız yeni fayllar yarada bilər (məsələn, əlavə partial-klasslar, metodlar və s.). Bu o deməkdir ki, əgər sənin klassın partial kimi elan olunubsa, ona əlavə metodlar və property-lər generasiya etmək mümkündür.

Bəzən sintaksisi düzgün parseləmək və dilin bütün incəliklərini (nested klasslar, generics, modifier-lər və s.) nəzərə almaq çətin olur. Generator müəllifi təmin etməlidir ki, generasiya olunmuş kod kompilyasiya oluna bilsin və layihəni "qırmasın".

Başqa bir tələ — əgər interface-ı implement edən metodlar generasiya edirsinizsə, faylların hər build-də yenidən generasiya olunduğuna əmin olun. Əks halda qəribə kompilyasiya xətaları ola bilər. Müasir alətlər bu məsələləri adətən həll edir, amma bunu yadda saxlamaq lazımdır.

Source Generators vs reflection

Reflection: runtime-da çağırılır, resurs tələb edir, compiler tərəfindən yoxlanılmır, böyük həcmdə yavaş ola bilər.

Source Generator: compilasiya mərhələsində kod yaradır. Hər şey statik yoxlanılır, IDE metodları görür, intellisense işləyir və performans adi C# kodu kimi olur.

Praktiki faydalar

  • System.Text.Json: reflection olmadan serialization/deserialization generasiyası.
  • DI konteynerlərinin dizaynı: misal üçün Microsoft.Extensions.DependencyInjection üçün dependency graflarının generasiyası.
  • Mapper-lər kimi Mapster: reflection-dan compile-time mapping koduna keçid.
  • Test framework-lər: atributlara əsaslanaraq test metodlarının avtomatik generasiyası.
  • ASP.NET Minimal APIs (NET 7-dən başlayaraq): endpoint-handler-lərin generasiyası.

Konfiqurasiya, parametrlər və seçimlər

Generator-ları MSBuild parametrləri, əlavə fayllar və konvensiyalar vasitəsilə konfiqurasiya etmək olar. Məsələn, müxtəlif ToString generasiya etmək olar mühitə görə (Debug/Release) və ya tətbiq konfiqurasiyasına görə.

Source Generators real-world tapşırıqlarla necə bağlıdır

Təmiz və sürətli koda can atan inkişaf etdirici üçün bu əla alətdir: daha az rutinə, daha çox compile-time yoxlamalarına, IDE təkliflərinə və aydın refaktoringə imkan verir. Generatorlar haqqında biliklər serialization-dan və DI-dən tutmuş mapping-ə qədər müsahibələrdə getdikcə daha çox tələb olunur.

Source Generator-un yaşam dövrü

Mərhələ Nə baş verir
1. Layihə qoşulur Sənin generatorun analyzer/reference kimi əlavə olunur
2. Roslyn source-u kompilyasiya edir Generator AST (abstrakt sintaksis ağacı) alır
3. Generator işə düşür Yeni .cs faylları kompilyasiyaya əlavə edir
4. Hər şey kompilyasiya olunur Generasiya olunmuş fayllar sizin assembly-nin bir hissəsinə çevrilir
5. Kod hazırdır! Generasiya olunmuş metodlar/klasslar çağırıla bilər

5. Debuq və tipik səhvlər

Generator yazarkən yeni başlayanların ən çox etdiyi səhvlərdən biri — əlavə etmək istədiyiniz klassda partial açar sözünü unutmaqdır. Əgər partial qoymasanız, kompilyator sizin əlavə etdiyiniz kodu görməyəcək. Bəzən generasiya olunmuş fayl IDE-də ilk rebuild-ə qədər görünməyə bilər — qorxmayın.

Generasiya olunmuş faylları adlandırarkən diqqətli olun: əgər hamısına eyni adı versəniz, onlar bir-birini üstələyəcək. Hiylə: faylın adına generasiya olunan klassın adını əlavə edin, məsələn, context.AddSource($"{className}_ToString", ...).

Atributların iki dəfə import olunması xətası — əgər generasiya etdiyiniz klass layihədə artıq mövcud olan atributu bir daha yaradırsa, konflikt yaranacaq. Lazım olan atributları ümumi layihəyə çıxarmaq və ya generasiya olunan kodu yalnız ehtiyac olduqda əlavə etmək daha yaxşıdır.

2
Tapşırıq
C# SELF, səviyyə, dərs
Bağlanıb
ToString metodunun avtomatik generasiyası
ToString metodunun avtomatik generasiyası
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION