What are C# source generators?

Source Generators dynamically add C# code to your consuming project on compilation time.
We use the .NET Compiler Platform ("Roslyn") API to analyse our code.

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

Why we need source generators?

  • They reduce boilerplate for repetitive code tremendously
  • Only one place (in the source generator) to change / update repetitive code
  • Therefore less error prone
  • Practical examples of source generators?

    Personally I built a source generator for link generation in my ASP.NET Core application.
    The source generator creates a LinkBuilder for every route / controller action with the corresponding action parameters.

    A list of OpenSource .NET Source Generators on github

    How C# source generators work?

    Technically speaking they are like an HTML parser for C# (or VB.NET) code.
    When you build your project (dotnet build), your code (of the consuming assembly) get parsed into syntax trees.
    We can navigate through every syntax tree and start analyse our code and (optionally) add new or extend (partial) C# code.

    var syntaxTree = CSharpSyntaxTree.ParseText(@"Console.WriteLine(""Hello Source Generator!"")");
    

    (When building a source generator, we get the syntax trees directly from the IIncrementalGenerator interface without explicitly write the parsing logic.)

    Syntax vs semantic model?

    A SyntaxNode from a syntax tree gives you static information about the source code (basically everything as a string).
    We can receive the SemanticModel for every SyntaxNode which gives coherent / meaningful information.
    e.g. variable value, type info, etc.

    using Microsoft.CodeAnalysis;
    using Microsoft.CodeAnalysis.CSharp;
    using Microsoft.CodeAnalysis.CSharp.Syntax;
    
    var syntaxTree = CSharpSyntaxTree.ParseText(
        @"public class Counter
        {
            public const int CurrentValue = 12345;
        }");
    
    // Syntax:
    var root = syntaxTree.GetRoot();
    var textFieldSyntax = root.DescendantNodes().OfType().First();
    
    // SemanticModel:
    var compilation = CSharpCompilation.Create(assemblyName: "Counter")
        .AddReferences(MetadataReference.CreateFromFile(typeof(object).Assembly.Location))
        .AddSyntaxTrees(syntaxTree);
    var semanticModel = compilation.GetSemanticModel(syntaxTree);
    var textFieldInfo = (IFieldSymbol)semanticModel.GetDeclaredSymbol(textFieldSyntax.Declaration.Variables.First())!;
    
    Console.WriteLine(textFieldInfo.Type.Name); // Int32
    Console.WriteLine(textFieldInfo.ConstantValue); // 12345
    Console.WriteLine(textFieldInfo.ConstantValue!.GetType().Name); // Int32
    

    Closing word

    I think source generators are one of the most interesting thing in .NET / C#.
    They open so much possibilities to improve productivity.