Become a 10x .NET Dev with C# Source Generators

I finally got the chance to use an exciting new feature of .NET 5, Source Generators! 

As you've probably already guessed by the name, Source Generators generate C# code as a part of the build process. They enable you to dynamically add new code during the compilation process. 

Source Generators examine the existing code that you've written and determine what new code to generate based on the existing code. They will only add new generated code, not modify anything existing.

Creating a Simple C# Source Generator

 

Step 1: Creating the Source Generator

 

First, we’ll  need to define the Source Generator. We can do this by creating a new project and referencing it in the project that we want to generate code for. FYI, you’ll need to have Visual Studio with .NET 5 Preview installed.

 

Ok so open VS and create a new C# .NET Standard 2.0 Class Library Project project called “BalloonSourceGenerator”. 

 

Next you’ll double click on the project file to modify it to look like the following:

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <TargetFramework>netstandard2.0</TargetFramework>
        <LangVersion>preview</LangVersion>
    </PropertyGroup>
     
    <PropertyGroup>
        <RestoreAdditionalProjectSources>https://pkgs.dev.azure.com/ktolly/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>

Save the project file and build the project to check there are no errors.

 

Next you’ll define the actual Source Generator. Add a new class named “Generator” that implements the ISourceGenerator interface. Decorate the class with the [Generator] attribute. Both of these come from the Microsoft.CodeAnalysis namespace:

using System;
using Microsoft.CodeAnalysis;
 
 
namespace BalloonSourceGenerator
{
    [Generator]
    public class Generator : ISourceGenerator
    {
        public void Execute(SourceGeneratorContext context)
        {
            throw new NotImplementedException();
        }
 
        public void Initialize(InitializationContext context)
        {
            throw new NotImplementedException();
        }
    }
}
 

The magic happens in the Execute method while the Initialize method is reserved for configuring more complex scenarios. 

To keep things simple, we’ll just add a new class to the compilation. But in the real world, you’ll probably add some actual logic here.

using System;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
 
namespace BalloonSourceGenerator
{
    [Generator]
    public class Generator : ISourceGenerator
    {
        public void Execute(SourceGeneratorContext context)
        {
            const string source = @"
namespace GeneratedBalloon
{
    public class BalloonChooser
    {
        public string BestBalloonsForBirthdays => ""Colorful Latex"";
        public string BestBalloonsForWeddings => ""Monochromatic foil"";
    }
}
";
            const string desiredFileName = "BalloonChooser.cs";
             
            SourceText sourceText = SourceText.From(source, Encoding.UTF8); // If no encoding specified then SourceText is not debugable
 
            // Add the "generated" source to the compilation
            context.AddSource(desiredFileName, sourceText);
        }
 
        public void Initialize(InitializationContext context)
        {
            // Advanced usage
        }
    }
}

A key thing to note is that the SourceGeneratorContext passed to the Execute method is the object that allows us to add the source to the compilation.

Now let’s build the project and move on to step 2. 

 

Step 2: Register the C# Source Generator in a Project

Add a new .NET Core Console project to the solution called “BalloonConsole”. Once that’s created, go ahead and add a reference to the BalloonSourceGenerator project. This will ensure that the console app generates the source code as a part of the build. 

Here’s what your project file should look like:

<Project Sdk="Microsoft.NET.Sdk">
 
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>
 
  <ItemGroup>
    <ProjectReference Include="..\Balloon\BalloonSourceGenerator.csproj" />
  </ItemGroup>
 
</Project>

To opt-in to the code generation, we need to modify the BalloonConsole project file to add <LangVersion>preview</LangVersion> and change the reference to the generator project to be an analyzer reference:

<Project Sdk="Microsoft.NET.Sdk">
 
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net5.0</TargetFramework>
    <LangVersion>preview</LangVersion>
  </PropertyGroup>
 
  <ItemGroup>
      <ProjectReference Include="..\Balloon\BalloonSourceGenerator.csproj"
                        OutputItemType="Analyzer"
                        ReferenceOutputAssembly="false"/>
  </ItemGroup>
 
</Project>
 

Now let’s build the project and move on to step 3. 

Step 3: Use the Generated Code

In the Program.cs file located in the console app, add a using directive to the namespace that was used in the source code string using GeneratedBalloons;

In the Main method we can now instantiate and use BalloonChooser. As you add the following code, you’ll notice that you get Intellisense support when referencing the BestBalloonForBirthdays and BestBalloonForWeddings props.

Here’s what the Program.cs file should look like:

using System;
using GeneratedBalloons;
 
namespace BalloonConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            var balloonChooser = new BalloonChooser();
 
            Console.WriteLine($"The best balloons for birthdays are: {balloonChooser.BestBalloonsForBirthdays}");
            Console.WriteLine($"The best balloons for weddings are: {balloonChooser.BestBalloonsForWeddings}");
 
            Console.ReadLine();
        }
    }
}
Now, if you run the console app you should see the following:
The best balloons for birthdays are: Colorful Latex
The best balloons for weddings are: Monochromatic foil

 

Leave a Comment

Close Bitnami banner
Bitnami