Debugging a Simple C# Source Generator

Brand new to the world of C# is the concept of a source generator. A compiler extention which lets you inject code into your build before compilation. This was possible before by plugging custom build targets into the build system, but now you’ll get access to Roslyn’s syntax tree as you do it!

Prerequisites

Microsoft’s own blog will have better information, but the purpose of this post is to demonstrate how to debug. Source generators are a little strange, since they run as you build… not as you are actually debugging.

Debugging

Since the feature is still in preview, debugging is tough. The easiest way is to add a “Debugger.Launch()” in the Initialize method of the source generator. Here is an example with a very simple source generator.

namespace SimpleSourceGenerator
{
    using Microsoft.CodeAnalysis;
    using Microsoft.CodeAnalysis.CSharp;
    using Microsoft.CodeAnalysis.Text;
    using System.Diagnostics;
    using System.Text;

    /// <summary>
    /// Debuggable source generator.
    /// </summary>
    [Generator]
    internal class DebuggableSourceGenerator : ISourceGenerator
    {
        public void Execute(SourceGeneratorContext context)
        {
            const string ClassName = "TestClass";

            // Define a class named "TestClass" in the namespace SimpleSourceGenerator.Generated
            var testClass = SyntaxFactory.CompilationUnit()
                .AddMembers(SyntaxFactory.NamespaceDeclaration(SyntaxFactory.IdentifierName("SimpleSourceGenerator.Generated"))
                    .AddMembers(SyntaxFactory.ClassDeclaration(ClassName)));

            // Add the test class to the output.
            context.AddSource(ClassName + ".cs", SourceText.From(testClass.NormalizeWhitespace().ToFullString(), encoding: Encoding.UTF8));
        }

        public void Initialize(InitializationContext context)
        {
            // Attach the debugger.
            Debugger.Launch();
        }
    }
}

This will launch the debugger ever time the source generate gets initialized… which is quite often, so its only useful if you’re actively developing the generator. To kick off the source generator, just build/rebuild the project which references it. (In this example that’s SimpleConsoleApp). You can attach the same window of Visual Studio that you’re currently developing in as the debugger.

For completeness, here is what the rest of a simple source generator project looks like.

namespace SimpleConsoleApp
{
    using System;
    // This is the namespace we defined in the source generator as the output.
    using SimpleSourceGenerator.Generated;

    class Program
    {
        static void Main(string[] args)
        {
            // This is the test class which gets generated by the source generator.
            var test = new TestClass();
        }
    }
}
<!-- SimpleSourceGenerator.csproj This is the project file for the source generator.-->
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <LangVersion>preview</LangVersion>
  </PropertyGroup>

  <PropertyGroup>
    <RestoreAdditionalProjectSources>https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5/nuget/v3/index.json;$(RestoreAdditionalProjectSources)</RestoreAdditionalProjectSources>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.6.0-3.20207.2" PrivateAssets="all" />
    <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.0.0-beta2.final" PrivateAssets="all" />
  </ItemGroup>
</Project>
<!-- SimpleConsoleApp.csproj This is a simple console app which references the source generator.-->
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <LangVersion>preview</LangVersion>
  </PropertyGroup>

  <ItemGroup>
    <ProjectReference Include="..\SimpleSourceGenerator\SimpleSourceGenerator.csproj"
                      ReferenceOutputAssembly="false"
                      OutputItemType="Analyzer" />
  </ItemGroup>

</Project>

Leave a Reply

Your email address will not be published. Required fields are marked *