Source Generators: Reflection'a Alternatif

Reflection'ın çalışma zamanı maliyetine karşılık, source generator'lar ile derleme sırasında kod üreterek performans kazancı sağlanır. Örneklerle source generator yazımı gösterilir.

Source Generators: Reflection'a Alternatif

Source Generators: Reflection'a Alternatif (Build-Time Kod Üretimi)

Reflection (Type.GetProperty, MethodInfo.Invoke), .NET'in en güçlü araçlarından biridir ancak bedeli ağırdır: Çalışma zamanında tip meta verilerini taramak, JIT optimizasyonlarını engellemek, bellek tahsisi (alloc) yapmak ve özellikle Native AOT (ahead-of-time) derlemelerinde tamamen çalışmaz hale gelmek.

Source Generators (Kaynak Üreticileri), bu sorunu devrimsel bir şekilde çözer: Reflection'ı çalışma zamanında değil, derleme zamanında çalıştırarak doğrudan C# kod dosyaları üretir. Üretilen bu kod, projenizin geri kalanıyla birlikte derlenir ve hiçbir runtime maliyeti olmaz.


1. Reflection'ın 3 Büyük Problemi

  • Performans: PropertyInfo.SetValue çağrısı, doğrudan property atamasından yaklaşık 50-100 kat daha yavaştır. Her çağrıda güvenlik kontrolleri (security checks) ve tip dönüşümleri yapılır.

  • AOT Uyumsuzluğu: .NET Native veya iOS (Mono) gibi ortamlarda, derleyici hangi tiplerin reflection ile kullanılacağını önceden bilemez, bu nedenle metot gövdeleri (body) kesilip atılabilir (trimming). Bu da MissingMethodException fırlatır.

  • Bakım Zorluğu: "Sihirli string" (ör. "GetUser") kullanımı refactoring'i imkansızlaştırır ve hataları runtime'a taşır.


2. Source Generator Nasıl Çalışır? (Mantık)

  1. Siz kodunuzda özel bir Attribute (Öznitelik) tanımlarsınız (ör. [GenerateAutoMap]).

  2. Derleme başladığında, Source Generator çalışır, SyntaxReceiver aracılığıyla bu attribute'ün kullanıldığı tüm sınıfları yakalar.

  3. Generator, bu sınıfları analiz eder (property'lerine bakarak) ve elle yazılmış gibi optimize edilmiş bir mapper kodu üretir (örn. MapToDTO metodu).

  4. Bu üretilen .cs dosyası, obj klasörüne yazılır ve ana derlemeye (compilation) dahil edilir.

Sonuç: Üretilen kod, sizin elinizle yazdığınız koddan farksızdır; ne reflection ne de ekstra alloc maliyeti vardır.


3. Adım Adım Basit Bir Source Generator Yazmak

Örnek: Sınıflara [ToJson] attribut'ü ekleyerek, o sınıfın JSON string'ini döndüren bir GetJson() uzantı metodu üretelim.

Adım 1: Generator projesi oluşturun (.NET Standard 2.0 hedeflemeli).

xml

<!-- .csproj -->
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" PrivateAssets="all" />
    <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
  </ItemGroup>
</Project>

Adım 2: Incremental Generator implementasyonu (EF Core 6+ ve .NET 8'de standart olan hızlı pipeline).

csharp

[Generator]
public class ToJsonGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // Attribute'ün kullanıldığı sınıfları bul
        var classDeclarations = context.SyntaxProvider
            .ForAttributeWithMetadataName(
                "MyApp.GenerateJsonAttribute", // Attributün tam adı
                (node, _) => true,
                (ctx, _) => ctx.TargetSymbol as INamedTypeSymbol
            )
            .Where(symbol => symbol is not null);

        // Derleme bilgisiyle birleştir ve kaynağı ekle
        context.RegisterSourceOutput(classDeclarations.Collect(), (ctx, symbols) =>
        {
            foreach (var classSymbol in symbols)
            {
                var namespaceName = classSymbol.ContainingNamespace.ToDisplayString();
                var className = classSymbol.Name;
                
                // Özellikleri topla
                var properties = classSymbol.GetMembers().OfType<IPropertySymbol>()
                    .Where(p => p.DeclaredAccessibility == Accessibility.Public)
                    .Select(p => p.Name);

                // Üretilecek kodu inşa et (string interpolation)
                var source = $@"
using System.Text.Json;

namespace {namespaceName}
{{
    public static class {className}Extensions
    {{
        public static string ToJson(this {className} obj)
        {{
            return JsonSerializer.Serialize(obj);
        }}
    }}
}}";
                // Kaynağı ekle
                ctx.AddSource($"{className}.Generated.cs", source);
            }
        });
    }
}

Adım 3: Attribute'ü tanımlayın (Ana projede veya shared bir projede).

csharp

namespace MyApp
{
    [AttributeUsage(AttributeTargets.Class)]
    public class GenerateJsonAttribute : Attribute { }
}

Adım 4: Ana projede tüketin.

csharp

[GenerateJson]
public partial class User
{
    public string Name { get; set; }
    public int Age { get; set; }
}

// Artık UserExtensions.ToJson() otomatik olarak derleme anında üretildi!
var user = new User { Name = "Ahmet", Age = 30 };
string json = user.ToJson(); // Reflection yok, direkt Serialize çağrısı!

4. En Önemli Püf Noktaları ve Tuzaklar

  • Incremental Generator Kullanın: Eski ISourceGenerator yerine IIncrementalGenerator kullanın. Bu, derleyicinin değişen dosyaları cache'leyerek büyük projelerde derleme süresini inanılmaz hızlandırır.

  • ForAttributeWithMetadataName ile Tanımlama: C# 12 ile gelen bu metot, sizin manuel SyntaxReceiver yazmanızı engeller, otomatik olarak attribute'leri filtreler.

  • Debug Etmek: Geliştirme sırasında generator'un neden çalışmadığını anlamak için System.Diagnostics.Debugger.Launch(); ekleyip Visual Studio'yu yeniden başlatarak adım adım ilerleyebilirsiniz. (Üretim kodunda kaldırmayı unutmayın).

  • Derleme Determinizmi: Generator içinde Guid.NewGuid(), DateTime.Now veya rastgele (random) işlemler KULLANMAYIN. Aksi takdirde her derlemede farklı çıktı üretir ve derleme önbelleği (incremental build) bozulur.

  • Bağımlılıklar: Generator projeniz, analiz ettiği kaynak koda erişebilir, ancak çalışma zamanı kütüphanelerine (ör. System.Text.Json) referans vermek zorunda değildir, çünkü sadece string üretir. Ürettiği kod, ana projenin referanslarına göre derlenir.


5. Gerçek Dünya Kullanım Alanları

Eğer bu konsepti sevdiyseniz, işte .NET ekosisteminde Source Generator kullanan devasa projeler:

  • System.Text.Json Source Generator: JsonSerializerContext ile reflection-free JSON serileştirme sağlar. Performansı Newtonsoft'un 10 katına çıkarabilir.

  • Microsoft.Extensions.DependencyInjection: IServiceCollection'a eklenen servisleri derleme anında çözümleyerek ServiceProvider oluşturmayı hızlandırır.

  • AutoMapper: Artık klasik CreateMap yerine Source Generator tabanlı mapper'lar tercih ediliyor (örn. Mapperly kütüphanesi).

Sonuç:

Reflection, prototipleme ve esneklik için hala değerlidir. Ancak üretimde (prod) ve yüksek trafikli sistemlerde, Source Generator kullanmak bugünün standardıdır. Size tavsiyem: Bir sonraki projenizde, reflection ile yaptığınız her işlemi sorgulayın: "Bunu derleme zamanında yapabilir miyim?" Cevabınız 'Evet' ise, bir Source Generator yazmak için harika bir fırsatınız var demektir.

Tüm yazılar