What is Source Generator
Overview
Section titled “Overview”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.
Learning Objectives
Section titled “Learning Objectives”Core Learning Objectives
Section titled “Core Learning Objectives”- 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
- 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
- Learn the basic structure of source generators
- The
IIncrementalGeneratorinterface and pipeline composition approach
- The
Source Generator Definition
Section titled “Source Generator Definition”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.
Key Characteristics
Section titled “Key Characteristics”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.
1. Compile-Time Execution
Section titled “1. Compile-Time Execution”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 }}2. Additive Only
Section titled “2. Additive Only”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 -> Impossible3. Deterministic Output
Section titled “3. Deterministic Output”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.
Basic Structure of Source Generators
Section titled “Basic Structure of Source Generators”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); }); }}Components
Section titled “Components”| Component | Role |
|---|---|
[Generator] attribute | Tells the compiler this class is a source generator |
IIncrementalGenerator | Incremental source generator interface |
Initialize method | Generator initialization and pipeline configuration |
SyntaxProvider | Selects nodes of interest from source code |
RegisterSourceOutput | Outputs generated code |
Now that we understand the basic structure, let’s compare how source generators differ from existing code generation technologies.
Source Generator vs Other Technologies
Section titled “Source Generator vs Other Technologies”Comparison with T4 Templates
Section titled “Comparison with T4 Templates”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 changesComparison with Reflection.Emit
Section titled “Comparison with Reflection.Emit”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 neededAs 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.
Real-World Use Cases
Section titled “Real-World Use Cases”Source generators are used in various scenarios:
1. JSON Serialization (System.Text.Json)
Section titled “1. JSON Serialization (System.Text.Json)”[JsonSerializable(typeof(User))]public partial class MyJsonContext : JsonSerializerContext{}// -> Serialization code generated at compile time2. Logging (Microsoft.Extensions.Logging)
Section titled “2. Logging (Microsoft.Extensions.Logging)”[LoggerMessage(Level = LogLevel.Information, Message = "User {UserId} logged in")]public static partial void LogUserLogin(ILogger logger, int userId);// -> High-performance logging code generated3. Dependency Injection
Section titled “3. Dependency Injection”[RegisterService]public class UserService : IUserService{}// -> DI container registration code generatedSummary at a Glance
Section titled “Summary at a Glance”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.