C# Source Generators
Reduce boilerplate with compile-time code generation.
By EMEPublished: February 20, 2025
csharpsource generatorscode generationmetaprogramming
A Simple Analogy
Source Generators are like hiring someone to write repetitive code for you. You describe what you want, and the compiler generates it automatically during build.
What Are Source Generators?
Source Generators run at compile-time, analyzing your code and generating new C# code. No runtime reflection needed—pure compile-time magic.
Why Use Source Generators?
- Zero runtime cost: Generation happens at build
- Type safety: All errors caught at compile-time
- Boilerplate elimination: Auto-generate repetitive code
- Better performance: No reflection overhead
- IDE support: Generated code is visible
Basic Generator Structure
[Generator]
public class AutoNotifyGenerator : ISourceGenerator
{
public void Initialize(GeneratorInitializationContext context)
{
// Register receivers if needed
}
public void Execute(GeneratorExecutionContext context)
{
// Find classes marked with [AutoNotify]
var compilation = context.Compilation;
var symbols = compilation.GlobalNamespace
.GetMembers()
.OfType<INamedTypeSymbol>();
foreach (var symbol in symbols)
{
if (HasAttribute(symbol, "AutoNotify"))
{
var source = GenerateNotifyCode(symbol);
context.AddSource($"{symbol.Name}_AutoNotify.g.cs", source);
}
}
}
private string GenerateNotifyCode(INamedTypeSymbol symbol)
{
var properties = symbol.GetMembers()
.OfType<IPropertySymbol>();
var code = new StringBuilder();
code.AppendLine($"partial class {symbol.Name} : INotifyPropertyChanged");
code.AppendLine("{");
code.AppendLine(" public event PropertyChangedEventHandler PropertyChanged;");
foreach (var prop in properties)
{
code.AppendLine($" private {prop.Type} _{Char.ToLowerInvariant(prop.Name[0])}{prop.Name.Substring(1)};");
}
code.AppendLine("}");
return code.ToString();
}
}
Usage Example
// Mark class with attribute
[AutoNotify]
public partial class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
// Generator creates:
// partial class Person : INotifyPropertyChanged
// {
// public event PropertyChangedEventHandler PropertyChanged;
// private string _firstName;
// // ... rest of implementation
// }
Practical Example: Dependency Injection
[Generator]
public class ServiceProviderGenerator : ISourceGenerator
{
public void Execute(GeneratorExecutionContext context)
{
var compilation = context.Compilation;
var services = FindServicesWithAttribute(compilation, "Singleton");
var code = GenerateServiceProvider(services);
context.AddSource("GeneratedServiceProvider.g.cs", code);
}
private string GenerateServiceProvider(List<ServiceInfo> services)
{
var sb = new StringBuilder();
sb.AppendLine("public class GeneratedServiceProvider : IServiceProvider");
sb.AppendLine("{");
foreach (var service in services)
{
sb.AppendLine($" public {service.Interface} Get{service.Name}()");
sb.AppendLine(" {");
sb.AppendLine($" return new {service.Implementation}();");
sb.AppendLine(" }");
}
sb.AppendLine("}");
return sb.ToString();
}
}
Best Practices
- Keep generators simple: Focus on one task
- Provide diagnostics: Help users fix issues
- Document behavior: Explain what gets generated
- Test thoroughly: Generate correct code
- Version carefully: Changes affect compilation
Related Concepts
- Incremental generators for performance
- Syntax trees and semantic analysis
- Analyzer integration
- Code generation frameworks (T4)
Summary
Source Generators eliminate boilerplate through compile-time code generation. Use them to auto-generate serializers, dependency injection containers, and notification implementations.