BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage News Source Generators Will Enable Compile-Time Metaprogramming in C# 9

Source Generators Will Enable Compile-Time Metaprogramming in C# 9

This item in japanese

Source generators are a new feature of the C# compiler that enables inspecting user code using compiler-generated metadata and generating additional source files to be compiled along with the rest of program.

Loosely inspired by F# type providers, C# source generators respond to the same aim of enabling metaprogramming but in a completely different way. Indeed, while F# type providers emit types, properties, and methods in-memory, source generators emit C# code back into the compilation process.

Source generators cannot modify existing code; they can only add new code to the compilation. Another limitation of source generators is they cannot be applied to code emitted by other source generators. This ensures each code generator will see the same compilation input regardless of the order of their application. Interestingly, source generators are not limited to inspecting source code and its associated metadata, but they may access additional files.

Specifically, source generators are not designed to be used as code rewriting tools, such as optimizers or code injectors, nor are they meant to be used to create new language features, although this would be technically feasible to some limited extent. Examples of use cases the C# team explicitly targets with source generators are automatic interface implementation, data serialization, and so on. A richer list of use cases, including discussion of suggested approaches to solve them, can be found in the source generator cookbook.

Source generators are strictly related to Roslyn code analyzers, as is made evident by the interface that defines them:

namespace Microsoft.CodeAnalysis
{
    public interface ISourceGenerator
    {
        void Initialize(InitializationContext context);
        void Execute(SourceGeneratorContext context);
    }
}

The Initialize method is called by the compiler and gives the generator a chance to register a set of callbacks that will be called later. The Execute method is where code generation occurs. It receives a SourceGeneratorContext object that provides access to the current Compilation object.

namespace Microsoft.CodeAnalysis
{
    public readonly struct SourceGeneratorContext
    {
        public ImmutableArray<AdditionalText> AdditionalFiles { get; }

        public CancellationToken CancellationToken { get; }

        public Compilation Compilation { get; }

        public ISyntaxReceiver? SyntaxReceiver { get; }

        public void ReportDiagnostic(Diagnostic diagnostic) { throw new NotImplementedException(); }

        public void AddSource(string fileNameHint, SourceText sourceText) { throw new NotImplementedException(); }
    }
}

The SourceGeneratorContext object can be modified to include additional code using AddSource. As mentioned above, source generators are not limited to C# files only. This is reflected by the AdditionalFiles collection, which contains any additional files passed to the compiler.

Summing all this up, this is how you can define a trivial source generator for an "hello world" program:

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;

namespace SourceGeneratorSamples
{
    [Generator]
    public class HelloWorldGenerator : ISourceGenerator
    {
        public void Execute(SourceGeneratorContext context)
        {
            // begin creating the source we'll inject into the users compilation
            var sourceBuilder = new StringBuilder(@"
using System;
namespace HelloWorldGenerated
{
    public static class HelloWorld
    {
        public static void SayHello() 
        {
            Console.WriteLine(""Hello from generated code!"");
            Console.WriteLine(""The following syntax trees existed in the compilation that created this program:"");
");

            // using the context, get a list of syntax trees in the users compilation
            var syntaxTrees = context.Compilation.SyntaxTrees;

            // add the filepath of each tree to the class we're building
            foreach (SyntaxTree tree in syntaxTrees)
            {
                sourceBuilder.AppendLine($@"Console.WriteLine(@"" - {tree.FilePath}"");");
            }

            // finish creating the source to inject
            sourceBuilder.Append(@"
        }
    }
}");

            // inject the created source into the users compilation
            context.AddSource("helloWorldGenerator", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8));
        }

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

Microsoft has released more introductory examples to show developers how they can use this new feature.

Source generators are available in .NET 5 preview and the latest Visual Studio preview. The feature is still in its early days and its API and characteristics will likely change in future releases leading to GA with C# 9.

Rate this Article

Adoption
Style

BT