Skip to content

Latest commit

 

History

History

README.md

Roslyn Source Generators - ToString Auto-Generation

Modern C# source generator that automatically creates ToString() methods at compile time using Roslyn APIs.

🎯 What You'll Learn

  • Roslyn Source Generators: IIncrementalGenerator pattern (C# 9+)
  • Compile-Time Code Generation: Zero runtime overhead
  • Syntax Analysis: Filtering with predicates for performance
  • Semantic Analysis: Full type information with ISymbol
  • Attribute-Based Generation: Marker attributes for code gen triggers

📁 Project Structure

SourceGenerators/
├── SourceGenerators.sln
├── Generator/                    # Source generator project
│   ├── Generator.csproj          # netstandard2.0 (required for analyzers)
│   ├── ToStringGenerator.cs      # IIncrementalGenerator implementation
│   └── ToStringAttribute.cs      # Marker attribute
└── Consumer/                      # Application using the generator
    ├── Consumer.csproj            # References generator as analyzer
    ├── Person.cs                  # Classes with [GenerateToString]
    └── Program.cs                 # Demonstration

🚀 Quick Start

Build and Run

cd samples/04-Expert/SourceGenerators

# Build both projects
dotnet build

# Run consumer application
dotnet run --project Consumer

Expected Output

🔧 ROSLYN SOURCE GENERATORS - ToString Auto-Generation

═══ Example 1: Person ═══
Person { Name = John Doe, Age = 30, Email = john@example.com }

═══ Example 2: Product ═══
Product { Name = Laptop, Price = 999.99 }

═══ Example 3: Order ═══
Order { OrderId = 1001, CustomerName = Alice Smith, TotalAmount = 1500.50, OrderDate = ... }

💡 How It Works

1. Mark Classes with Attribute

[GenerateToString]  // ← Triggers source generator
public partial class Person  // ← Must be partial
{
    public string Name { get; set; }
    public int Age { get; set; }
}

2. Generator Creates Code at Compile Time

The source generator runs during compilation and creates Person.g.cs:

// <auto-generated/>
partial class Person
{
    public override string ToString()
    {
        return $"Person { Name = {Name}, Age = {Age} }";
    }
}

3. Use Generated Code

var person = new Person { Name = "John", Age = 30 };
Console.WriteLine(person);  // Uses generated ToString()
// Output: Person { Name = John, Age = 30 }

🔍 View Generated Files

Generated files are written to the build output:

# View generated code
cat Consumer/obj/Debug/net8.0/generated/Generator/SourceGenerators.ToStringGenerator/Person.g.cs

📊 Source Generator Pipeline

IIncrementalGenerator Approach (Modern)

public void Initialize(IncrementalGeneratorInitializationContext context)
{
    // STEP 1: Syntax filter (fast, no semantic analysis)
    var classDeclarations = context.SyntaxProvider
        .CreateSyntaxProvider(
            predicate: IsSyntaxTargetForGeneration,   // Quick check
            transform: GetSemanticTargetForGeneration) // Full analysis
        .Where(m => m is not null);

    // STEP 2: Combine with compilation
    var compilationAndClasses = context.CompilationProvider
        .Combine(classDeclarations.Collect());

    // STEP 3: Generate source
    context.RegisterSourceOutput(compilationAndClasses, Execute);
}

Benefits:

  • ✅ Only re-generates when relevant code changes
  • ✅ IDE remains responsive during editing
  • ✅ Faster build times with incremental compilation

🆚 Comparison: Source Generator vs Alternatives

Approach Compile Time Runtime Type Safe IDE Support
Manual Code - Fast
Source Generator +100-500ms Fast
Reflection - Slow ⚠️
T4 Templates Slow Fast ⚠️ ⚠️

🔑 Key Concepts

Partial Classes

Source generators extend existing classes, so they must be partial:

// ✅ GOOD
[GenerateToString]
public partial class Person { }

// ❌ BAD - Won't compile
[GenerateToString]
public class Person { }  // Missing 'partial'

Attribute Properties

[GenerateToString(
    IncludePropertyNames = true,     // "Name = John" vs "John"
    IncludePrivateFields = false)]   // Only public properties
public partial class Person { }

Generator Project Requirements

<PropertyGroup>
  <TargetFramework>netstandard2.0</TargetFramework>  <!-- Required -->
  <EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
  <IsRoslynComponent>true</IsRoslynComponent>
</PropertyGroup>

Consumer Project Reference

<ProjectReference Include="..\Generator\Generator.csproj"
                  OutputItemType="Analyzer"          <!-- Treat as analyzer -->
                  ReferenceOutputAssembly="true" />  <!-- Can use attributes -->

🎓 Advanced Topics

Debugging Source Generators

  1. View generated files:

    # Enable EmitCompilerGeneratedFiles
    dotnet build /p:EmitCompilerGeneratedFiles=true
  2. Attach debugger:

    [Generator]
    public class ToStringGenerator : IIncrementalGenerator
    {
        public void Initialize(...)
        {
            System.Diagnostics.Debugger.Launch();  // Breaks here
        }
    }

Performance Optimization

  • Use predicates to filter early (syntax-only checks)
  • Use transforms for expensive semantic analysis
  • Cache compilation data between runs
  • Only generate when source changes (incremental)

📚 Real-World Applications

  1. JSON Serialization: Generate serializers at compile time
  2. Entity Framework: Generate DbContext from attributes
  3. gRPC/Protobuf: Generate clients from .proto files
  4. Regex: Compile regex patterns at build time (C# 11+)
  5. Logging: Generate structured logging code
  6. DTOs: Auto-generate mapping between models

🐛 Common Issues

Generator Not Running

# Clean build output
dotnet clean
rm -rf Consumer/obj Consumer/bin

# Rebuild
dotnet build

Can't Find Attribute

Ensure ReferenceOutputAssembly="true" in project reference:

<ProjectReference Include="..\Generator\Generator.csproj"
                  OutputItemType="Analyzer"
                  ReferenceOutputAssembly="true" />  <!-- This! -->

Partial Class Missing

// ❌ Error CS0260: Missing partial modifier
[GenerateToString]
public class Person { }

// ✅ Fixed
[GenerateToString]
public partial class Person { }

📖 Resources

✅ Learning Checklist

  • Understand IIncrementalGenerator vs ISourceGenerator
  • Write predicates for syntax filtering
  • Use semantic analysis with ISymbol
  • Generate source with AddSource()
  • Debug generated code
  • Reference generator as analyzer
  • Use partial classes correctly
  • View generated files in obj/ directory

Key Takeaway: Source generators eliminate boilerplate while maintaining type safety and zero runtime overhead. They're compile-time magic that makes your code cleaner!