1. Giriş
Gəlin dürüst olaq: əksər insanlar kod generasiyası barədə təxminən eyni şeyi düşünürlər — yumurtanı dilimləyən maşın kimi: faydalıdır, amma nadir hallarda həyati vacib. Amma müasir .NET layihələri get-gedə mürəkkəbləşir və rutinin və ya şablon işin avtomatlaşdırılması təkcə "hiylət" deyil, keyfiyyət və məhsuldarlığın artırılması üçün vacib alətdir.
Source Generators — bu mexanizm C# 9 və .NET 5-dən etibarən gəldi. Onlar kompilyasiya mərhələsində super qəhrəman kimi C# kodu yarada bilər və sonra bu kod layihənin bir hissəsi kimi kompilyasiya olunur. Onlar runtime zamanı artıq kompilyasiya olunmuş assambleyalara müdaxilə etmirlər, sadəcə kompilyasiyadan əvvəl layihəni yeni mənbə faylları ilə genişləndirirlər.
Aşağıda Source Generators-in tipik istifadə ssenariləri veriləcək.
Şablon kodunun avtomatik yaradılması
Bir çox layihədə eyni tipli kod yazmaq lazım gəlir: konstruktorlar, ToString metodları, serializatorlar, property change notification (INotifyPropertyChanged) və s. Source Generators bu kodu avtomatik yarada bilər, inkişaf etdiriciləri rutindən və yazı səhvlərindən azad edir.
Nümunə: ToString Generator
Tutaq ki, siz DTO-klasları (Data Transfer Object) yazırsınız. Hər sinif üçün mənalı bir ToString implementasiyası lazımdır ki, bütün propertylər sadə şəkildə sıralansın.
Əllə köçürməyə ehtiyac yoxdur — hər sinifə [AutoToString] atributu qoyulduqda Source Generator avtomatik olaraq ToString metodu yarada bilər. Məsələn, AutoToString buna bənzər şəkildə işləyir.
// Sizin sinif atributla
[AutoToString]
public partial class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
// Source Generator təxminən belə yaradacaq:
public partial class Person
{
public override string ToString() => $"Person: Name={Name}, Age={Age}";
}
Üstünlüklər: kod oxunaqlı olur və yeni property əlavə ediləndə avtomatik yenilənir.
Serializasiya və deserializasiyanın sadələşdirilməsi
Source Generators standart kitabxananın içində də geniş istifadə olunur — məsələn, System.Text.Json sürətli serializasiya/deserializasiya kodunu generasiya edir. Generatordan əvvəl serializasiya çox vaxt reflection tələb edirdi (performans baxımından bahalı), indi isə generasiya edilmiş kod yüksək performansla işləyir.
İstifadəçi nə əldə edir? [JsonSerializable(typeof(MyType))] qoyursunuz — və generator həmin tip üçün performanslı serializasiya kodu yaradır.
Konfiqurasiyalar, mapperlər, DI-konteynerlər üçün kod generasiyası
- Konfiqlər: generatorlar JSON fayllara əsaslanaraq konfiqurasiya klasslarını avtomatik yarada bilər.
- Mapperlər: məsələn, Mapster generator vasitəsilə tiplər arasında mapping yaradır, əllə sahə-köçürmə yazmağa ehtiyac qalmır.
- Dependency Injection: bəzi konteynerlər (məsələn, StrongInject) servis qeydiyyatı üçün kodu generatorlarla yaradır.
Xarici infrastruktura inteqrasiyası
Bəzi generatorlar xarici resursları (API təsvirləri, GraphQL sxemləri, Thrift və s.) analiz edib işləmək üçün C# klassları yaradır. Bu, müqavilələr dəyişəndə kodu əl ilə yeniləmək ehtiyacını aradan qaldırır.
Komilyasiya mərhələsində kodun yoxlanması və diaqnostika
Source Generators kod yoxlama üçün də istifadə oluna bilər: "Əgər layihədə metod X varsa, amma şərt Y təmin olunmayıbsa — kompilatorda xəbərdarlıq!". Bir çox linter və analyzer belə işləyir, generatorlar isə öz mesajlarını əlavə edə və ya hazır stub kod yerləşdirə bilər.
2. Avtomatik serializasiya üçün öz Source Generator
Sadə nümunəyə baxaq: atributu olan sinif üçün [AutoJson] JSON serializasiya metodu yaradan generator.
Növbəti kod yalnız təsvir üçündür; real dünyada System.Text.Json.SourceGeneration-dən istifadə edin.
// Əllə yazdığımız:
[AutoJson]
public partial class Book
{
public string Title { get; set; }
public int Year { get; set; }
}
// Generator əlavə edəcək:
public partial class Book
{
public string ToJson() => $"{{ \"Title\": \"{Title}\", \"Year\": {Year} }}";
}
Source Generator ilə layihənin qarşılıqlı əlaqə sxemi
flowchart TD
A(Sizin Source Code) -->|Kompilyator çağırır| B(Source Generator)
B -->|Əlavə olunan Code| C(Yeni .cs fayllar)
C --> D(Layihənin kompilyasiyası)
- Əvvəlcə kompilyator (Roslyn) sizin mənbələri analiz edir.
- Sonra sizin generator (ISourceGenerator implementasiyası) çağırılır.
- Generator yeni .cs faylları əlavə edir, onlar ümumi kompilyasiya ağacının bir hissəsinə çevrilir.
- Nəticədə assembly həm sizin, həm də generasiya edilmiş kodu ehtiva edir.
İxtisaslaşmış tapşırıqlar: nəyi daha generasiya etmək olar
- Native kitabxanalar üçün bindings (C kodu və ya WinAPI üçün wrapperlar).
- AOP: avtomatik çağırışların loglanması, aspektlər (Fody, PostSharp tərzi).
- API təsviri avtomatik sənədləşdirmə generatorları üçün.
- Xarici resursların emalı: SVG, SQL, Razor — generator strongly-typed klasslar yaradır ki, tip təhlükəsiz giriş olsun.
3. Runtime-da kodu dinamik yaratmaq: System.Reflection.Emit
Əgər Source Generators kompilyasiyadan əvvəl C# kodu yaradırsa, System.Reflection.Emit runtime zamanı sehr yaradır. Proqramınız öz-özünə yeni tiplər, metodlar və hətta assambleyalar yarada bilər — canlı olaraq!
Qorxunc səslənir? Biraz. Amma bəzən başqa yol yoxdur: dinamik proxy (AOP, profiling, mocking), runtime-dakı məlumatlara əsaslanan serializatorlar, dinamik ORM və s. üçün lazım olur.
Reflection.Emit nə vaxt lazımdır
- Tip əvvəlcədən məlum deyil (istifadəçi strukturu runtime-da təyin edir).
- Dinamik proxylər (çağırışları ələ keçirən wrapperlər).
- Yüksək performanslı serializasiya (məsələn, protobuf-net kimi həllər).
- Pluginlər və script mühərrikləri — mürəkkəb yükləmə ssenariləri üçün.
Reflection.Emit ilə nə yaratmaq olar
- AssemblyBuilder — yeni assambleya yaratmaq.
- ModuleBuilder — assambleyada modul.
- TypeBuilder — yeni tipin təsviri.
- MethodBuilder — IL kodlu metod.
- PropertyBuilder, FieldBuilder, EventBuilder — propertylər, fieldlər, eventlər.
Mini-nümunə: uçuş zamanı yeni klass yaratmaq
using System;
using System.Reflection;
using System.Reflection.Emit;
public static class DynamicTypeGenerator
{
public static Type GenerateSimpleType(string typeName)
{
// 1. Assambleya və modul yaradırıq
var assemblyName = new AssemblyName("DynamicAssembly");
var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
var moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");
// 2. Yeni klass yaradırıq
var typeBuilder = moduleBuilder.DefineType(
typeName,
TypeAttributes.Public | TypeAttributes.Class
);
// 3. Public string property Title əlavə edirik
var field = typeBuilder.DefineField("_title", typeof(string), FieldAttributes.Private);
var prop = typeBuilder.DefineProperty("Title", PropertyAttributes.HasDefault, typeof(string), null);
var getMethod = typeBuilder.DefineMethod("get_Title", MethodAttributes.Public, typeof(string), Type.EmptyTypes);
var il = getMethod.GetILGenerator();
il.Emit(OpCodes.Ldarg_0); // this
il.Emit(OpCodes.Ldfld, field); // _title
il.Emit(OpCodes.Ret);
prop.SetGetMethod(getMethod);
var setMethod = typeBuilder.DefineMethod("set_Title", MethodAttributes.Public, null, new[] { typeof(string) });
il = setMethod.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Stfld, field);
il.Emit(OpCodes.Ret);
prop.SetSetMethod(setMethod);
// 4. Hazırdır! Type yaradırıq
return typeBuilder.CreateTypeInfo();
}
}
İndi bu tipi adi C# obyekt kimi istifadə etmək olar, məsələn reflection vasitəsilə:
var dynamicType = DynamicTypeGenerator.GenerateSimpleType("Book");
var obj = Activator.CreateInstance(dynamicType);
dynamicType.GetProperty("Title").SetValue(obj, "C# v ictimaiyyetde");
Console.WriteLine(dynamicType.GetProperty("Title").GetValue(obj)); // C# v ictimaiyyetde
Belə yaranır ORM-lər, proxylər, serializatorlar, profilerlər və bəzi test frameworkləri.
4. Faydalı nüanslar
Source Generators vs Reflection.Emit: kim nədir?
Source Generators kompilyasiya mərhələsində işləyir: mənbə kodunuzu daha ağıllı edir və nəticə kompilyasiya olunmuş assambleyada olur. Onlar runtime məlumatlarına əsasən kod yaratmaq üçün istifadə edilə bilməz.
Reflection.Emit runtime-da işləyir: assambleyalar, tiplər və metodlar dinamik yaradılır, amma belə kodu debug etmək və saxlamaq daha çətindir.
Hansı halda nə istifadə etmək?
Gündəlik analoqiya:
- Source Generators — maşını yığmazdan əvvəl hazırlanmış detallar istehsal edən fabrik kimidir.
- Reflection.Emit — səyahət zamanı mütəxəssis tərəfindən avtomobilə raket mühərriki qaynaq edən mühəndis kimidir.
Xüsusiyyətlər və potensial tələlər
- Generasiya olunmuş kodun debug edilməsi çətin ola bilər. Generatorlarda bir çox vaxt mənbə faylları diskə yazmaq imkanı olur — onları obj\Generated qovluğunda axtarın.
- Reflection.Emit yaddaşda assambleyalar yaradır, bunlar AppDomain-dən yüklənmir. Müvəqqəti assambleyalar üçün AssemblyBuilderAccess.RunAndCollect-dən istifadə edin (əgər dəstəklənirsə).
- Sadə tapşırıqlar üçün Reflection.Emit-dən sui-istifadə etməyin — bəzən source generator və ya adi şablon daha sadə və etibarlı olur.
- Generatorlar üçün Roslyn, Reflection.Emit üçün isə IL bilikləri tələb olunur.
Source Generators və Reflection.Emit
| Kriteriy | Source Generators | Reflection.Emit |
|---|---|---|
| İstifadə olunur | Kompilyasiya mərhələsində | Runtime-da |
| Nəticə | C# mənbələri, assembly-nin hissəsi | IL-kod, yeni tiplər/assambleyalar |
| Tipik ssenarilər | Şablon kodun avtomatik yaradılması, DI, mapping, serializasiya | Proxy, dinamik ORM, xüsusi runtime-pipeline-lar |
| İstifadə çətinliyi | Orta, Roslyn bilikləri tələb olunur | Yüksək, IL bilikləri tələb olunur |
| IDE və debug dəstəyi | Əladır (mənbələr görünür) | Çətindir |
| Performans | Çox yüksək | Yüksək ola bilər |
5. Praktiki ssenarilər
1. Strongly-typed API generasiyası
Təşkilat OpenAPI spesifikasiyası verir. Generator onu analiz edib controller klassları, DTO və REST API ilə işləmək üçün client kodu yaradır — tip təhlükəsiz və IntelliSense dəstəyi ilə.
Kod (pseudo):
// Spec: GET /users -> returns User[]
// Source Generator yaradacaq:
public class ApiClient
{
public Task<User[]> GetUsersAsync() { ... }
}
2. Avtomatik injection/DI-konteyner (Compile-time IoC)
Generatorlar dependency registrasiyası üçün builderlər yaradır və obyekt qrafını qurur. Artıq əllə services.AddSingleton<IMyService, MyService>() yazmağa ehtiyac yoxdur.
Kod (pseudo):
[Injectable]
public class MyService : IMyService { ... }
// Source Generator yaradacaq:
partial class DIContainer
{
public void RegisterServices()
{
AddSingleton<IMyService, MyService>();
}
}
3. Dinamik proxylər — Reflection.Emit nümunəsi
< a target="_blank" href="https://www.nuget.org/packages/Castle.Core/">Castle DynamicProxy kimi kitabxanalar metod çağırışlarını ələ keçirən proxy tipləri qurur — AOP, logging, tracing, mocking üçün əsastır.
Kod (sadələşdirilmiş):
public interface IBookService { string GetBook(); }
public class BookService : IBookService { public string GetBook() => "C#"; }
var proxy = ProxyGenerator.CreateProxy<IBookService>(new BookService(), interceptor);
proxy.GetBook(); // Çağırış ələ keçirilir, loglama və ya nəticəni dəyişmək olar
4. Reflection olmadan sürətli serializasiya
Runtime-da reflection ilə tip təsvirləri qurmaqdansa (yavaşdır), Source Generators serializasiya/deserializasiya kodunu əvvəlcədən yarada bilər — maksimum sürət və minimal overhead.
GO TO FULL VERSION