Loading...

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?


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

  1. Create a new Class Library (.NET Standard) project.
  2. 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

FeatureSource GeneratorReflection
TimingCompile TimeRuntime
PerformanceHigh (pre-generates code)Lower (runtime analysis)
Use CasesORM, Serialization, API ClientPlugins, Testing, Dynamic Types
Code VisibilityGenerated files are visible in the projectEffective only at runtime

Performance & Best Practices


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 ISourceGenerator or IIncrementalGenerator interfaces.
  • 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