Skip to content

What is Source Generator

As software grows more complex, writing repetitive code becomes an unavoidable reality. When code with identical patterns — like logging, serialization, and validation — is written by hand every time, mistakes occur and consistency breaks down. Source Generator, built into the C# compiler, solves this problem at compile time. Because it analyzes existing code and automatically generates the necessary code, developers can focus solely on core logic.

  1. Understand the definition and operating principles of source generators
    • Identify when source generators execute in the compilation pipeline
    • Understand the significance of running after syntax and semantic analysis, but before IL generation
  2. Grasp the concept of compile-time code generation
    • Understand the fundamental difference from runtime code generation
    • The design principles of Additive Only and Deterministic output
  3. Learn the basic structure of source generators
    • The IIncrementalGenerator interface and pipeline composition approach

Source Generator is an extension of the C# compiler that analyzes source code during the compilation process and automatically generates new C# code.

Position of Source Generator in the Compilation Process
=======================================================
Source Code (.cs)
|
v
+-------------------+
| Syntax Analysis | Syntax Analysis
| (Parsing) |
+---------+----------+
|
v
+-------------------+
| Semantic Analysis | Semantic Analysis
| (Binding) |
+---------+----------+
|
v
+-------------------+
| Source Generator | <- Runs here!
| (Generator) |
+---------+----------+
|
v
+-------------------+
| Code Generation | IL Code Generation
| (Emit) |
+---------+----------+
|
v
Assembly (.dll)

Source generators run after the compiler has analyzed the source code, but before IL code generation. At this point, they can analyze the structure of existing code and generate additional code as needed. The ObservablePortGenerator we will implement in this tutorial also analyzes adapter classes at this exact point to automatically create Observability code.

Now that we understand this operating principle, let’s examine the three key characteristics of source generators.


The design of source generators rests on three principles. They execute only at compile time, never modify existing code, and guarantee identical results for identical inputs. Let’s explore why each of these principles matters.

Source generators execute at compile time, not at runtime:

// Code written by the developer
[GenerateObservablePort]
public class UserRepository : IObservablePort
{
public FinT<IO, User> GetUserAsync(int id) => ...;
}
// Code auto-generated by the compiler (by the source generator)
public partial class UserRepositoryObservable
{
public FinT<IO, User> GetUserAsync(int id)
{
// Logging, metrics, etc. - auto-generated code
}
}

Since they execute at compile time, could they modify existing code freely? No. Source generators cannot modify or delete existing code. They can only add new code:

Source Generator Constraints
============================
O Add new files -> Possible
O Add new classes -> Possible
O Extend partial class -> Possible
X Modify existing code -> Impossible
X Delete existing code -> Impossible
X Overwrite files -> Impossible

The same input must always produce the same output. This is essential for incremental builds and caching. Because ObservablePortGenerator follows this principle, the same adapter class always generates the same Observable wrapper code.

These three characteristics serve as the safety net for source generators. Now let’s look at the actual structure of how source generators are written.


All source generators implement the IIncrementalGenerator interface:

using Microsoft.CodeAnalysis;
[Generator]
public class MyGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// 1. Select parts of interest from source code (register Provider)
var provider = context.SyntaxProvider
.ForAttributeWithMetadataName(
"MyNamespace.MyAttribute",
predicate: (node, _) => node is ClassDeclarationSyntax,
transform: (ctx, _) => GetClassInfo(ctx));
// 2. Register code generation logic
context.RegisterSourceOutput(provider, (spc, classInfo) =>
{
var code = GenerateCode(classInfo);
spc.AddSource($"{classInfo.Name}.g.cs", code);
});
}
}
ComponentRole
[Generator] attributeTells the compiler this class is a source generator
IIncrementalGeneratorIncremental source generator interface
Initialize methodGenerator initialization and pipeline configuration
SyntaxProviderSelects nodes of interest from source code
RegisterSourceOutputOutputs generated code

Now that we understand the basic structure, let’s compare how source generators differ from existing code generation technologies.


T4 templates have long been the standard for code generation in the .NET ecosystem, but they have a fundamental limitation of being separated from the compiler.

T4 Templates
=============
- Requires separate .tt files
- Runs at design time
- Generated code must be included in source control
- Cannot auto-detect input source changes
Source Generator
================
- Integrated with the compiler
- Runs at compile time
- Generated code not needed in source control
- Auto-regenerated when input changes

Reflection.Emit is a powerful tool that directly generates IL code at runtime, but it cannot be used in AOT environments and is difficult to debug.

Reflection.Emit
================
- Generates IL code directly at runtime
- Difficult to debug
- Compatibility issues with AOT compilation
- High learning curve
Source Generator
================
- Generates C# code at compile time
- Generated code can be debugged
- Full AOT compilation support
- Only C# syntax knowledge needed

As shown, source generators solve the shortcomings of existing technologies while providing the unique advantage of tight integration with the compiler. In fact, they are already widely used in the .NET ecosystem.


Source generators are used in various scenarios:

[JsonSerializable(typeof(User))]
public partial class MyJsonContext : JsonSerializerContext
{
}
// -> Serialization code generated at compile time
[LoggerMessage(Level = LogLevel.Information, Message = "User {UserId} logged in")]
public static partial void LogUserLogin(ILogger logger, int userId);
// -> High-performance logging code generated
[RegisterService]
public class UserService : IUserService
{
}
// -> DI container registration code generated

Source generators are extensions of the C# compiler that execute at the point after syntax/semantic analysis and before IL generation. They follow the additive-only model where existing code cannot be modified, and guarantee the same output for the same input. They are written by implementing the IIncrementalGenerator interface and provide advantages in performance, type safety, debuggability, and AOT support.


Q1: What is the difference between source generators and runtime code generation?

Section titled “Q1: What is the difference between source generators and runtime code generation?”

A: Source generators produce C# source code at compile time, so there is no runtime overhead and the generated code can be directly inspected with a debugger. In contrast, runtime code generation (Reflection.Emit, Expression Trees, etc.) operates during application execution, incurs performance costs, and has constraints in AOT environments.

Q2: Why is the “Additive Only” constraint necessary?

Section titled “Q2: Why is the “Additive Only” constraint necessary?”

A: If existing code could be modified or deleted, changes from multiple simultaneously running source generators could conflict, leading to unpredictable results. The additive-only model fundamentally prevents such conflicts and guarantees the integrity of the original code written by developers.

Q3: What is the difference between IIncrementalGenerator and the older ISourceGenerator?

Section titled “Q3: What is the difference between IIncrementalGenerator and the older ISourceGenerator?”

A: ISourceGenerator re-executed the entire generation logic every time source changed, but IIncrementalGenerator provides an incremental pipeline that only reprocesses changed parts. This significantly improves build performance in large projects and is the currently officially recommended approach.


Now that we understand what source generators are, let’s look at the more specific reasons for choosing source generators over other technologies.

02. Why Source Generator