C# > Source Generators > Using Roslyn for Code Generation > Syntax Trees and Compilation

Simple Source Generator for Creating a Hello World Class

This code snippet demonstrates a basic source generator that automatically creates a 'HelloWorld' class with a 'SayHello' method. It showcases the fundamental steps of using Roslyn for code generation: creating a syntax tree, adding namespaces, classes, and methods, and then adding the generated code to the compilation process.

Core Concepts

Source Generators in C# allow you to inspect user code at compile time and generate additional C# source files that are compiled along with the rest of the project. This enables you to automate the creation of boilerplate code, implement compile-time code generation based on attributes, or optimize code based on static analysis.

The Source Generator Implementation

This code defines a source generator class `HelloWorldGenerator` that implements the `ISourceGenerator` interface. The `Execute` method is where the code generation logic resides. It constructs a string containing the C# code for a `HelloWorld` class with a `SayHello` method. Then, it adds this code as a source file named `HelloWorld.g.cs` to the compilation. The `Initialize` method is empty in this example but can be used for initialization tasks.

//Add the following nuget package to the project: Microsoft.CodeAnalysis.CSharp.Workspaces

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using System.Text;

namespace MySourceGenerator
{
    [Generator]
    public class HelloWorldGenerator : ISourceGenerator
    {
        public void Execute(GeneratorExecutionContext context)
        {
            // Build up the source code
            string sourceCode = $" 
using System;

namespace GeneratedCode
{{
    public static class HelloWorld
    {{
        public static string SayHello(string name)
        {{
            return $\"Hello, {{name}}!\";
        }}
    }}
}}";

            // Add the source code to the compilation
            context.AddSource("HelloWorld.g.cs", SourceText.From(sourceCode, Encoding.UTF8));
        }

        public void Initialize(GeneratorInitializationContext context)
        {
            // No initialization required for this one
        }
    }
}

Real-Life Use Case

Consider a scenario where you need to generate data transfer objects (DTOs) based on database schema at compile time. A source generator can read the schema and automatically create the corresponding DTO classes, eliminating the need to manually write repetitive code. Another example is generating code for implementing design patterns like the Factory pattern based on configurations.

Best Practices

  • Keep source generators focused and avoid complex logic.
  • Use incremental generators (`IIncrementalGenerator`) for performance.
  • Test your source generators thoroughly.
  • Handle errors gracefully and provide informative diagnostics.

Interview Tip

Be prepared to discuss the advantages and disadvantages of using source generators compared to other code generation techniques like T4 templates or reflection. Understand the compile-time nature of source generators and their impact on build performance.

When to use them

Source generators are useful when you need to generate code at compile time based on information available at compile time, such as attributes, project settings, or external data. They are a good choice for automating repetitive tasks, reducing boilerplate code, and improving performance by avoiding runtime code generation.

Memory Footprint

Source generators primarily impact the compile time, increasing the overall build time. The generated code contributes to the application's size, like any other compiled code. Optimizing the generated code reduces the memory footprint.

Alternatives

Alternatives to source generators include:

  • T4 Templates: Text Template Transformation Toolkit (T4) is another code generation tool, but it's executed before compilation and doesn't have access to the Roslyn APIs.
  • Reflection: Reflection allows you to generate code at runtime, but it can impact performance and introduce security vulnerabilities.
  • Post-Build Scripts: Executing scripts after the build to generate code.

Pros

  • Compile-Time Code Generation: Generates code during compilation, improving performance by avoiding runtime code generation.
  • Roslyn Integration: Leverages the Roslyn APIs for code analysis and generation.
  • Reduced Boilerplate: Automates the creation of repetitive code, reducing developer effort.

Cons

  • Increased Build Time: Source generators can increase the compilation time.
  • Complexity: Writing and debugging source generators can be complex.
  • Debugging Challenges: Debugging generated code can be challenging.

Steps to use the Source Generator

Add the source generator class to a separate project. This project should target .NET Standard 2.0. Add a nuget package reference of `Microsoft.CodeAnalysis.CSharp.Workspaces` to the source generator project. Reference the source generator project in your main application project. Build the application. The `HelloWorld` class will be automatically generated and available for use.

FAQ

  • How do I debug a source generator?

    You can debug a source generator by attaching a debugger to the `dotnet` process during compilation. Add `System.Diagnostics.Debugger.Launch()` in your source generator code to prompt the debugger to attach. Alternatively, you can use logging or diagnostic messages to track the execution of the source generator.
  • What happens if a source generator produces invalid C# code?

    If a source generator produces invalid C# code, the compilation will fail, and the compiler will report errors. It's important to handle errors gracefully in your source generator and provide informative diagnostic messages to help developers understand and fix the issues.