Skip to content

Source Generators

Giriş

Source Generators, .NET 5+ ile gelen compile-time code generation teknolojisidir. Mid-level geliştiriciler için source generators'ı anlamak, performance optimization, boilerplate code reduction ve compile-time validation için kritik öneme sahiptir. Bu dosya, source generator fundamentals, custom generators, incremental generators ve best practices konularını kapsar.

Source Generator Fundamentals

1. Basic Source Generator

Temel source generator implementasyonu.

[Generator]
public class HelloWorldGenerator : ISourceGenerator
{
    public void Initialize(GeneratorInitializationContext context)
    {
        // Register for syntax notifications
        context.RegisterForSyntaxNotifications(() => new HelloWorldSyntaxReceiver());
    }

    public void Execute(GeneratorExecutionContext context)
    {
        // Get the registered receiver
        if (context.SyntaxReceiver is not HelloWorldSyntaxReceiver receiver)
            return;

        // Generate source code
        var sourceBuilder = new StringBuilder(@"
using System;

namespace Generated
{
    public static class HelloWorld
    {
        public static void SayHello()
        {
            Console.WriteLine(""Hello from Source Generator!"");
        }
    }
}");

        // Add the generated source
        context.AddSource("HelloWorld.g.cs", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8));
    }
}

public class HelloWorldSyntaxReceiver : ISyntaxReceiver
{
    public List<ClassDeclarationSyntax> Classes { get; } = new();

    public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
    {
        if (syntaxNode is ClassDeclarationSyntax classDeclaration)
        {
            Classes.Add(classDeclaration);
        }
    }
}

2. Attribute-Based Generator

Attribute kullanarak source generation tetikleyen generator.

[Generator]
public class AutoToStringGenerator : ISourceGenerator
{
    public void Initialize(GeneratorInitializationContext context)
    {
        context.RegisterForSyntaxNotifications(() => new AutoToStringSyntaxReceiver());
    }

    public void Execute(GeneratorExecutionContext context)
    {
        if (context.SyntaxReceiver is not AutoToStringSyntaxReceiver receiver)
            return;

        foreach (var classDeclaration in receiver.Classes)
        {
            var className = classDeclaration.Identifier.ValueText;
            var properties = GetProperties(classDeclaration);

            var source = GenerateToStringMethod(className, properties);
            context.AddSource($"{className}.ToString.g.cs", SourceText.From(source, Encoding.UTF8));
        }
    }

    private string GenerateToStringMethod(string className, List<PropertyInfo> properties)
    {
        var propertyStrings = properties.Select(p => $"{p.Name} = {{{p.Name}}}");
        var toStringBody = string.Join(", ", propertyStrings);

        return $@"
using System;

namespace Generated
{{
    public partial class {className}
    {{
        public override string ToString()
        {{
            return $""{className} {{{toStringBody}}}"";
        }}
    }}
}}";
    }

    private List<PropertyInfo> GetProperties(ClassDeclarationSyntax classDeclaration)
    {
        return classDeclaration.Members
            .OfType<PropertyDeclarationSyntax>()
            .Select(p => new PropertyInfo { Name = p.Identifier.ValueText })
            .ToList();
    }
}

public class PropertyInfo
{
    public string Name { get; set; }
}

public class AutoToStringSyntaxReceiver : ISyntaxReceiver
{
    public List<ClassDeclarationSyntax> Classes { get; } = new();

    public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
    {
        if (syntaxNode is ClassDeclarationSyntax classDeclaration &&
            HasAutoToStringAttribute(classDeclaration))
        {
            Classes.Add(classDeclaration);
        }
    }

    private bool HasAutoToStringAttribute(ClassDeclarationSyntax classDeclaration)
    {
        return classDeclaration.AttributeLists
            .SelectMany(al => al.Attributes)
            .Any(a => a.Name.ToString() == "AutoToString");
    }
}

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

Incremental Source Generators

1. Performance-Optimized Generator

Incremental generation kullanan generator.

[Generator]
public class IncrementalGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // Create a pipeline for class declarations
        var classDeclarations = context.SyntaxProvider
            .CreateSyntaxProvider(
                predicate: static (s, _) => IsSyntaxTargetForGeneration(s),
                transform: static (ctx, _) => GetTargetForGeneration(ctx))
            .Where(static m => m is not null);

        // Generate the source
        context.RegisterSourceOutput(classDeclarations,
            static (spc, source) => Execute(source!, spc));
    }

    private static bool IsSyntaxTargetForGeneration(SyntaxNode node)
        => node is ClassDeclarationSyntax { AttributeLists.Count: > 0 };

    private static ClassDeclarationSyntax? GetTargetForGeneration(GeneratorSyntaxContext context)
    {
        var classDeclarationSyntax = (ClassDeclarationSyntax)context.Node;

        foreach (AttributeListSyntax attributeListSyntax in classDeclarationSyntax.AttributeLists)
        {
            foreach (AttributeSyntax attributeSyntax in attributeListSyntax.Attributes)
            {
                if (IsAutoToStringAttribute(attributeSyntax))
                {
                    return classDeclarationSyntax;
                }
            }
        }

        return null;
    }

    private static bool IsAutoToStringAttribute(AttributeSyntax attributeSyntax)
    {
        var name = attributeSyntax.Name.ToString();
        return name == "AutoToString" || name == "AutoToStringAttribute";
    }

    private static void Execute(ClassDeclarationSyntax classDeclaration, SourceProductionContext context)
    {
        var className = classDeclaration.Identifier.ValueText;
        var namespaceName = GetNamespace(classDeclaration);

        var source = GenerateSource(className, namespaceName);
        context.AddSource($"{className}.g.cs", SourceText.From(source, Encoding.UTF8));
    }

    private static string GetNamespace(ClassDeclarationSyntax classDeclaration)
    {
        var parent = classDeclaration.Parent;
        while (parent is not NamespaceDeclarationSyntax namespaceDeclaration)
        {
            parent = parent?.Parent;
        }
        return namespaceDeclaration?.Name.ToString() ?? "Generated";
    }

    private static string GenerateSource(string className, string namespaceName)
    {
        return $@"
using System;

namespace {namespaceName}
{{
    public partial class {className}
    {{
        public string GeneratedProperty {{ get; set; }} = ""Generated by Source Generator"";

        public void GeneratedMethod()
        {{
            Console.WriteLine(""This method was generated at compile time"");
        }}
    }}
}}";
    }
}

Advanced Source Generators

1. JSON Serialization Generator

JSON serialization için custom generator.

[Generator]
public class JsonSerializerGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        var classDeclarations = context.SyntaxProvider
            .CreateSyntaxProvider(
                predicate: static (s, _) => s is ClassDeclarationSyntax,
                transform: static (ctx, _) => ctx.Node as ClassDeclarationSyntax)
            .Where(static m => m is not null);

        context.RegisterSourceOutput(classDeclarations,
            static (spc, source) => GenerateJsonSerializer(source!, spc));
    }

    private static void GenerateJsonSerializer(ClassDeclarationSyntax classDeclaration, SourceProductionContext context)
    {
        var className = classDeclaration.Identifier.ValueText;
        var properties = GetSerializableProperties(classDeclaration);

        if (!properties.Any()) return;

        var source = GenerateJsonSerializerSource(className, properties);
        context.AddSource($"{className}.JsonSerializer.g.cs", SourceText.From(source, Encoding.UTF8));
    }

    private static List<PropertyInfo> GetSerializableProperties(ClassDeclarationSyntax classDeclaration)
    {
        return classDeclaration.Members
            .OfType<PropertyDeclarationSyntax>()
            .Where(p => !HasIgnoreAttribute(p))
            .Select(p => new PropertyInfo 
            { 
                Name = p.Identifier.ValueText,
                Type = p.Type?.ToString() ?? "object"
            })
            .ToList();
    }

    private static bool HasIgnoreAttribute(PropertyDeclarationSyntax property)
    {
        return property.AttributeLists
            .SelectMany(al => al.Attributes)
            .Any(a => a.Name.ToString() == "JsonIgnore");
    }

    private static string GenerateJsonSerializerSource(string className, List<PropertyInfo> properties)
    {
        var serializationCode = string.Join("\n            ", 
            properties.Select(p => $"\"{p.Name}\": {GetJsonValue(p)}"));

        var deserializationCode = string.Join("\n            ", 
            properties.Select(p => $"{p.Name} = json[\"{p.Name}\"].{GetDeserializationMethod(p.Type)}"));

        return $@"
using System;
using System.Text.Json;
using System.Collections.Generic;

namespace Generated
{{
    public partial class {className}
    {{
        public string ToJson()
        {{
            return JsonSerializer.Serialize(new
            {{
                {serializationCode}
            }});
        }}

        public static {className} FromJson(string json)
        {{
            var jsonDoc = JsonDocument.Parse(json);
            var root = jsonDoc.RootElement;

            return new {className}
            {{
                {deserializationCode}
            }};
        }}
    }}
}}";
    }

    private static string GetJsonValue(PropertyInfo property)
    {
        return property.Type switch
        {
            "string" => $"\"{property.Name}\"",
            "int" or "long" or "double" or "decimal" => property.Name,
            "bool" => property.Name,
            _ => $"JsonSerializer.Serialize({property.Name})"
        };
    }

    private static string GetDeserializationMethod(string type)
    {
        return type switch
        {
            "string" => "GetString()",
            "int" => "GetInt32()",
            "long" => "GetInt64()",
            "double" => "GetDouble()",
            "decimal" => "GetDecimal()",
            "bool" => "GetBoolean()",
            _ => "GetString()"
        };
    }
}

[AttributeUsage(AttributeTargets.Property)]
public class JsonIgnoreAttribute : Attribute
{
}

Mülakat Soruları

Temel Sorular

  1. Source Generators nedir ve neden kullanılır?
  2. Cevap: Compile-time code generation teknolojisi. Performance improvement, boilerplate reduction, compile-time validation için.

  3. Source Generators vs Reflection farkı nedir?

  4. Cevap: Source generators compile-time, reflection runtime. Source generators daha performanslı, type-safe.

  5. Incremental Generators nedir?

  6. Cevap: Performance-optimized generators, sadece değişen kısımları regenerate eder.

  7. Source Generators ne zaman kullanılır?

  8. Cevap: Boilerplate code, compile-time validation, performance-critical scenarios.

  9. Source Generators limitations nelerdir?

  10. Cevap: Compile-time only, no runtime access, limited debugging.

Teknik Sorular

  1. Custom Source Generator nasıl implement edilir?
  2. Cevap: ISourceGenerator interface, Initialize ve Execute methods, syntax analysis.

  3. Incremental Generator nasıl optimize edilir?

  4. Cevap: IIncrementalGenerator interface, syntax provider pipeline, conditional generation.

  5. Source Generator debugging nasıl yapılır?

  6. Cevap: Debugger.Launch(), conditional compilation, logging.

  7. Source Generator testing nasıl yapılır?

  8. Cevap: SourceGeneratorVerifier, expected output validation, unit testing.

  9. Source Generator performance nasıl ölçülür?

  10. Cevap: Compilation time measurement, memory usage, incremental generation efficiency.

Best Practices

  1. Generator Design
  2. Single responsibility principle uygulayın
  3. Performance optimize edin
  4. Error handling implement edin
  5. Documentation sağlayın

  6. Performance Optimization

  7. Incremental generation kullanın
  8. Conditional compilation implement edin
  9. Memory allocation minimize edin
  10. Caching strategies uygulayın

  11. Error Handling

  12. Compilation errors handle edin
  13. User-friendly error messages sağlayın
  14. Fallback mechanisms implement edin
  15. Validation logic ekleyin

  16. Testing & Debugging

  17. Unit tests yazın
  18. Integration tests implement edin
  19. Debugging tools ekleyin
  20. Performance testing yapın

  21. Documentation

  22. Usage examples sağlayın
  23. API documentation yazın
  24. Best practices document edin
  25. Troubleshooting guide oluşturun

Kaynaklar