Source Generators
Last modified on Tue 28 Nov 2023

What are Source Generators?

According to the official documentation, a source generator is a part of the .NET Compiler Platform ("Roslyn") SDK which allows developers to generate source files during compilation and add those files to the compilation object.

For this to be possible, source generators can retrieve a compilation object which contains all the code a developer has written. The most common use case for source generators is code analysis during compilation, after which more code is generated and added to the compilation object. It can also shift the controller discovery phase from startup to compile time, reducing startup time. Another usage of source generators can be in ASP.NET Core applications routing where it can be strongly typed with the necessary strings being generated as a compile-time detail. This approach could prevent a mistyped string literal leading to a request not hitting the correct controller.

The image below shows the process which happens during compilation if a developer uses source generators.

sourceGenerator

There are multiple advantages of using source generators which include better performance because the source generator is analyzing code during the compilation. An alternative to this approach is runtime reflection which is used during the startup of an application and therefore can prolong the startup time. Another advantage is the possibility to catch errors during compile time instead of catching them during runtime.

Project setup

Projects that contain source generators are just ordinary class libraries that require some adjustments. Here are some basic steps that can be used for source generator project setup.

  1. Create a new class library project with .NET Standard 2.0 target framework
  2. Add NuGet packages Microsoft.CodeAnalysis.Analyzers and Microsoft.CodeAnalysis.CSharp

    Here is an example of what a project file should look like after the first two steps:

    <Project Sdk="Microsoft.NET.Sdk">
    
    <PropertyGroup>
        <TargetFramework>netstandard2.0</TargetFramework>
    </PropertyGroup>
    
    <ItemGroup>
        <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" PrivateAssets="all" />
        <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3">
        <PrivateAssets>all</PrivateAssets>
        <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
        </PackageReference>
    </ItemGroup>
    
    </Project>
    
  3. Create an ExampleSourceGenerator.cs file which implements Microsoft.CodeAnalysis.ISourceGenerator interface and annotate it with Microsoft.CodeAnalysis.GeneratorAttribute

using Microsoft.CodeAnalysis;

namespace SourceGenerator
{
    [Generator]
    public class ExampleSourceGenerator : ISourceGenerator
    {
        public void Execute(GeneratorExecutionContext context)
        {
            // Code generation goes here
        }

        public void Initialize(GeneratorInitializationContext context)
        {
            // If required, initialization goes here
        }
    }
}

After these three steps your source generator is configured and to do something with it, you need to implement Execute method. Implementation of Initialize method is optional because it is not always required to initialize a source generator.

Debugging

If you've used source generators then you must know that it can be quite challenging to test the generator's output and find out what kind of error it throws.

Fortunately, Visual Studio comes to the rescue with its possibility to debug source generators after some easy steps:

  1. Install .NET Compiler Platform SDK

    In Visual Studio Installer choose to Modify the Visual Studio version which has a source generator project, and install Visual Studio extension development under the tab Workloads > Other Toolsets.

    visualStudioInstallerHome

    visualStudioModify

  2. Add the IsRoslynComponent property in the source generator project file:

    <Project Sdk="Microsoft.NET.Sdk">
        <PropertyGroup>
            <TargetFramework>netstandard2.0</TargetFramework>
            <IsRoslynComponent>true</IsRoslynComponent>
            <Nullable>enable</Nullable>
            <LangVersion>latest</LangVersion>
        </PropertyGroup>
        <ItemGroup>
            <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" PrivateAssets="all" />
            <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3">
                <PrivateAssets>all</PrivateAssets>
                <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
            </PackageReference>
        </ItemGroup>
    </Project>
    
  3. Add a reference to the source generator in a project that will be using it (if it isn't added already):

    <ItemGroup>
        <ProjectReference Include="..\JsonApi.SourceGenerator\JsonApi.SourceGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="true" />
    </ItemGroup>
    
  4. Add debug configuration using the following steps:

    1. Right-click on the project that contains the source generator
    2. Click Properties
    3. Click Debug
    4. Click Open debug launch profiles UI debugProperties
    5. Delete existing profiles deleteProfile
    6. Add a new Roslyn Component profile createProfile
    7. Choose the target project from a list of projects chooseTargetProject
    8. Optional: Rename the debug profile so you can easily find and use it later
    9. Restart Visual Studio and set your source generator project as a startup project

endResult

In the end, add some breakpoints to the source generator code, run the project in debug mode, resolve the issues that were bothering you in the first place or just explore how your source generator works in the background.

Sources: