Source Generators let you write C# code that runs during compilation, inspecting your existing code and producing new C# code that gets compiled along with everything else.

// MySourceGenerator.cs
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using System.Text;

[Generator]
public class MySourceGenerator : ISourceGenerator
{
    public void Execute(SourceGeneratorContext context)
    {
        // Get the source code for a specific class.
        // In a real scenario, you'd probably analyze existing code more deeply.
        string classCode = @"
public static class GeneratedHelper
{
    public static string GetGreeting()
    {
        return ""Hello from generated code!"";
    }
}
";
        // Add the generated code as a new source file to the compilation.
        context.AddSource("GeneratedHelper.cs", SourceText.From(classCode, Encoding.UTF8));
    }

    public void Initialize(InitializationContext context)
    {
        // Initialization logic, often used to register syntax or semantic receivers.
        // For this simple example, we don't need complex initialization.
    }
}

Now, imagine you have a class that needs a specific method, but you want to avoid writing it manually every time. Source Generators shine here. Let’s say you have a class MyService and you want to automatically generate a LogMessage method for it.

// MyService.cs (user code)
public class MyService
{
    // We want a LogMessage method to be generated for this class.
}

Your generator would look for classes and generate the method.

// LoggingGenerator.cs
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using System.Linq;
using System.Text;

[Generator]
public class LoggingGenerator : ISourceGenerator
{
    public void Execute(SourceGeneratorContext context)
    {
        // This is a simplified example. A real generator would parse
        // the compilation's syntax trees to find relevant types.
        // Here, we'll just generate a generic logging method.

        string generatedCode = @"
public static class Logger
{
    public static void LogMessage(string message)
    {
        System.Console.WriteLine($""[LOG] {message}"");
    }
}
";
        context.AddSource("Logger.cs", SourceText.From(generatedCode, Encoding.UTF8));
    }

    public void Initialize(InitializationContext context)
    {
        // No complex initialization needed for this example.
    }
}

After adding LoggingGenerator.cs to your project and rebuilding, you can now use Logger.LogMessage in your code, even though you never explicitly wrote it.

// Program.cs
public class Program
{
    public static void Main(string[] args)
    {
        Logger.LogMessage("Application started."); // This method was generated!
    }
}

The core problem Source Generators solve is reducing repetitive, boilerplate code that developers often write across many classes or projects. Think of things like INotifyPropertyChanged implementations, ToString() overrides, property setters that perform validation, or even entire API client stubs based on definitions. By generating this code at compile time, you get the benefits of having the code present (performance, no runtime reflection) without the burden of writing and maintaining it manually. The generator acts as a pre-compiler for your own code.

Internally, Source Generators work by hooking into the Roslyn compiler API. You implement ISourceGenerator, and the compiler invokes your Initialize and Execute methods. Initialize is where you can register "receivers" that tell the compiler to notify you about specific syntax nodes (like class declarations) or semantic information. Execute is where you perform your analysis and add new source text to the compilation using context.AddSource(). The generated code is treated as if it were part of your original project files.

One detail often overlooked is how generators interact with existing code. If a generator produces code that conflicts with user code (e.g., generating a method with the same name that already exists), the compiler will raise an error. Generators are designed to add code, not modify existing user code directly. This isolation is crucial for predictable behavior. You can also inspect the compilation context to understand what types, methods, and properties already exist, allowing your generator to make intelligent decisions about what code to produce. For instance, you might check if a method already exists before generating it, or you might generate code that extends an existing type with new members.

The next step in understanding Source Generators is exploring how to use Syntax and Semantic Receivers to analyze your project’s code structure and produce context-aware generated code.

Want structured learning?

Take the full Csharp course →