The Concept of Source Generators in C# (C# 9+)
Learn source generators in C# to generate code at compile time and improve performance with modern development techniques.
Introduced with C# 9, Source Generators are a powerful Roslyn feature that produces additional C# code at compile time. This mechanism creates new code files during compilation, not at runtime. As a result, repetitive code can be generated automatically with no runtime cost, boosting performance and reducing maintenance.
What Is a Source Generator?
A Source Generator is a component you add to the compiler. The compiler (Roslyn) analyzes the source code in your project and automatically adds new classes or methods where needed. The generated code is included at compile time and can be used by the developer just like normal C# code.
// Summary: A Source Generator is a compiler plugin.
// The developer writes custom logic; the generator emits code.
// At the end of the build, the generated code is automatically added to the project.
When Should You Use It?
- To automatically generate repetitive code that is tedious to write by hand
- To provide a compile-time alternative to reflection in performance-critical paths
- In ORM, serializer, mapper, proxy, event, and attribute-based systems
- When you need type-safe code generation, e.g., for JSON or API clients
A Simple Source Generator Example
Below is a minimal Source Generator example.
This generator creates a new HelloGenerated class during compilation.
// HelloGenerator.csproj (should be a separate Class Library)
// Package: Microsoft.CodeAnalysis.CSharp
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using System.Text;
[Generator]
public class HelloGenerator : ISourceGenerator
{
public void Initialize(GeneratorInitializationContext context)
{
// Runs at analysis start if needed (e.g., syntax listening)
}
public void Execute(GeneratorExecutionContext context)
{
string code = @"
namespace HelloGen
{
public static class HelloGenerated
{
public static void SayHello() =>
System.Console.WriteLine(""Hello! This class was generated at compile time."");
}
}";
context.AddSource("HelloGenerated.g.cs", SourceText.From(code, Encoding.UTF8));
}
}
// Usage in the main project
using HelloGen;
class Program
{
static void Main()
{
HelloGenerated.SayHello(); // Calls code generated at compile time
}
}
This code file is automatically created during compilation. In Visual Studio you can see it under “Analyzers → HelloGenerator → HelloGenerated.g.cs”.
Setup: Creating a Generator Project
- Create a new Class Library (.NET Standard) project.
- Add the following properties to the project file:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>latest</LangVersion>
<IncludeBuildOutput>false</IncludeBuildOutput>
<AnalyzerLanguage>C#</AnalyzerLanguage>
<OutputItemType>Analyzer</OutputItemType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.10.0" />
</ItemGroup>
</Project>
With this configuration, the project becomes usable as an “Analyzer/Generator”. Then reference this DLL as an “Analyzer” in the main project.
Attribute-Based Code Generation
In real-world scenarios, generators are often triggered via attributes.
For example, adding an [AutoToString] attribute to a class can generate a ToString() method at compile time.
// User code
[AutoToString]
public partial class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
// Code generated by the generator (at compile time)
public partial class Person
{
public override string ToString() =>
$""Person: Name={Name}, Age={Age}"";
}
This approach removes boilerplate code and simplifies maintenance.
Incremental Source Generator (C# 10+)
The Incremental Generator introduced with C# 10 provides better performance and incremental build support. Code is regenerated only for the files that changed.
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using System.Text;
[Generator]
public class SimpleIncrementalGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var classes = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: (syntaxNode, _) => syntaxNode is ClassDeclarationSyntax,
transform: (ctx, _) => (ClassDeclarationSyntax)ctx.Node)
.Collect();
context.RegisterSourceOutput(classes, (spc, classList) =>
{
foreach (var cls in classList)
{
string name = cls.Identifier.Text;
string code = $@"
namespace AutoGen
{{
public static class {name}Info
{{
public static void Write() =>
System.Console.WriteLine(""Class: {name}"");
}}
}}";
spc.AddSource($"{name}Info.g.cs", SourceText.From(code, Encoding.UTF8));
}
});
}
}
With this model, build performance improves significantly in large solutions. Generators now run only for the affected files.
Source Generator vs Reflection
| Feature | Source Generator | Reflection |
|---|---|---|
| Timing | Compile Time | Runtime |
| Performance | High (pre-generates code) | Lower (runtime analysis) |
| Use Cases | ORM, Serialization, API Client | Plugins, Testing, Dynamic Types |
| Code Visibility | Generated files are visible in the project | Effective only at runtime |
Performance & Best Practices
- Generators run in a read-only manner; they cannot modify existing files, only add new code.
- Generated code appears with a
.g.cssuffix under “Analyzers → Generated Files”. - Avoid heavy operations (e.g., file access, I/O) inside generators.
- Don’t generate unnecessary code that would slow down compilation.
- Emit UTF-8 text for generated sources and add them via
context.AddSource().
Example: Automatic DTO Generation
Suppose you want to generate DTO classes from models that correspond to database tables.
The Source Generator below creates a {ModelName}Dto class for each model.
// Model class
[AutoDto]
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
// Generated code
public class UserDto
{
public int Id { get; set; }
public string Name { get; set; }
}
This removes the burden of writing DTOs manually and automatically reflects model changes.
TL;DR
- Source Generators are Roslyn-based tools that generate code during compilation.
- They have no runtime cost like reflection; they are more efficient in terms of performance and security.
- Implemented via
ISourceGeneratororIIncrementalGeneratorinterfaces. - Generated code is included in the build and runs like ordinary C# code.
- Commonly used for serializers, mappers, DTO generators, and API client generation.
Related Articles
Code Analysis with Roslyn Compiler API in C#
Learn code analysis in C# using Roslyn Compiler API, including syntax trees, analyzers, and code generation scenarios.
Generics in C# (List<T>, Dictionary<TKey,TValue>)
Learn generics in C#, including List<T> and Dictionary<TKey,TValue>, to write type-safe and reusable code with examples.
Performance Optimization with Span<T> and Memory<T> in C#
Learn performance optimization in C# using Span<T> and Memory<T> to manage memory efficiently and process data faster.
Reflection and Late Binding in C#
Learn Reflection and late binding in C# to inspect types at runtime and build flexible, dynamic applications.
SOLID Principles with C#
Applying SOLID principles in C# with examples: building flexible, maintainable, and testable code.