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
MissingMethodExceptionfı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)
-
Siz kodunuzda özel bir Attribute (Öznitelik) tanımlarsınız (ör.
[GenerateAutoMap]). -
Derleme başladığında, Source Generator çalışır,
SyntaxReceiveraracılığıyla bu attribute'ün kullanıldığı tüm sınıfları yakalar. -
Generator, bu sınıfları analiz eder (property'lerine bakarak) ve elle yazılmış gibi optimize edilmiş bir mapper kodu üretir (örn.
MapToDTOmetodu). -
Bu üretilen
.csdosyası,objklasö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
ISourceGeneratoryerineIIncrementalGeneratorkullanın. Bu, derleyicinin değişen dosyaları cache'leyerek büyük projelerde derleme süresini inanılmaz hızlandırır. -
ForAttributeWithMetadataNameile Tanımlama: C# 12 ile gelen bu metot, sizin manuelSyntaxReceiveryazmanı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.Nowveya 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:
JsonSerializerContextile 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ümleyerekServiceProvideroluşturmayı hızlandırır. -
AutoMapper: Artık klasik
CreateMapyerine Source Generator tabanlı mapper'lar tercih ediliyor (örn.Mapperlykü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.