C# > Source Generators > Using Roslyn for Code Generation > Creating a Source Generator

Hello World Source Generator

This example demonstrates a basic source generator that adds a `HelloWorld` class with a `SayHello` method to the compilation. It's a minimal 'Hello, World!' for source generators.

Concepts Behind the Snippet

Source generators, introduced in C# 9, allow you to inspect user code and generate new C# source files that are added to the user's compilation. They operate during compilation, analyzing code to produce additional source code, which is then compiled alongside the original code. This avoids the runtime performance impact of reflection or code weaving techniques. This example showcases the basic structure of a source generator including the `Generator` attribute and the `Execute` method.

Implementation

This code defines a source generator named `HelloWorldGenerator`. The `Execute` method is the core of the generator. It creates a string containing the C# code for a `HelloWorld` class with a `SayHello` method. The `AddSource` method then adds this generated code to the compilation, effectively creating a new C# file named `HelloWorldGenerated.cs` during the build process. The `Initialize` method is intentionally left empty in this simple example, but it can be used for more complex generator setup.

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using System.Text;

namespace MySourceGenerator
{
    [Generator]
    public class HelloWorldGenerator : ISourceGenerator
    {
        public void Execute(GeneratorExecutionContext context)
        {
            // Generate the source code
            string sourceCode = $""" 
            namespace GeneratedCode
            {{
                public static class HelloWorld
                {{
                    public static string SayHello()
                    {{
                        return \"Hello, World! This was generated.\";
                    }}
                }}
            }}
            """;

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

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

Real-Life Use Case

This simple example can be extended to generate code based on attributes, interfaces, or other information in the user's code. Imagine automatically generating boilerplate code for dependency injection, data access, or implementing interfaces. This reduces the amount of repetitive code developers need to write manually, improving maintainability and reducing errors. For example, you could create a source generator that automatically implements `INotifyPropertyChanged` based on attributes applied to properties.

Best Practices

  • Keep source generators focused on specific tasks. Avoid creating overly complex generators that try to do too much.
  • Thoroughly test your source generators to ensure they generate correct code under various conditions.
  • Handle errors gracefully and provide informative error messages to the user.
  • Consider the impact on build times. While source generation happens during compilation, excessive or inefficient generators can slow down the build process.
  • Avoid introducing dependencies in the generated code that the user project doesn't have. The generator's job is to enhance, not encumber.

Interview Tip

Be prepared to explain the benefits of source generators over traditional code generation techniques like T4 templates or runtime reflection. Highlight the compile-time nature, the performance benefits, and the improved developer experience.

When to Use Them

Source generators are ideal when you need to generate code based on existing code, attributes, or configurations at compile time. They are particularly useful for generating boilerplate code, implementing design patterns, or creating domain-specific languages (DSLs). Avoid using them for tasks that can be handled more efficiently with standard code or runtime mechanisms.

Memory Footprint

The memory footprint of a source generator itself is generally small. However, the generated code can increase the overall size of the compiled assembly. It is important to ensure that the generated code is optimized to minimize its impact on memory usage.

Alternatives

Alternatives to source generators include:

  • T4 Templates: Older technology for generating code, less integrated with the compiler, and can be harder to debug.
  • Runtime Reflection: Can dynamically generate code at runtime, but suffers from performance overhead.
  • Code Weaving (e.g., PostSharp): Modifies existing code after compilation, which can be complex and potentially introduce runtime issues.

Pros

  • Compile-time code generation: Avoids runtime performance overhead.
  • Improved developer experience: Generates code automatically, reducing manual coding effort.
  • Type safety: Generated code is type-checked by the compiler.
  • Strong integration with the Roslyn compiler: Provides access to semantic information and diagnostics.

Cons

  • Increased build times: Complex source generators can slow down the build process.
  • Debugging complexity: Debugging generated code can be challenging.
  • Learning curve: Requires understanding of the Roslyn API and compiler concepts.

FAQ

  • How do I debug a source generator?

    You can debug a source generator by attaching a debugger to the `msbuild.exe` process during the build. You can also use the `Debugger.Launch()` method in your source generator code to trigger the debugger. Ensure you have the necessary symbols loaded for your generator project.
  • How do I reference external assemblies in a source generator?

    You can reference external assemblies by adding them as analyzer dependencies in your project file. This allows your generator to access types and methods from those assemblies.
  • Why isn't my source generator working?

    Common reasons include:
    • The generator is not correctly registered in the project file.
    • The generator is throwing an exception during execution. Check the build output for error messages.
    • The generator is not being triggered because the required syntax or attributes are not present in the user's code.