What are C# Source generators?
C# Source Generators were introduced as a novelty together with .NET 5. This is a form of meta programming, where we can machine-generate new source code based on metadata. As metadata, we can imagine various marking attributes, interfaces, but also, for example, conventions that you follow in your project (prefixes, suffixes, …) This is a native part of the Roslyn compiler.
„If you’re familiar with Roslyn Analyzers, you can think of Source Generators as analyzers that can emit C# source code.”
How does it work?
They added a new step to the compilation process (and it doesn’t matter if you wrote dotnet build and pressed F5 in Visual Studio, but even if you just edit some code and Roslyn analyzes the changed code) and that is starting your generator. This generation happens in two steps. In the first step, Roslyn offers information about what it is currently processing (you have at your disposal a syntactic and semantic model of the part of the code it is processing). In this step we can decide if we like it or not. If we like it, in the second step we will generate a new code based on this information. The generated code is added as input to the ongoing compilation and will be included in the resulting assembly.
We have to pinpoint that the idea of code generation is not new. Even in .NET we could do it long ago. We could make our own scripts and run them before the actual build. However, this brings us the advantage that the Roslyn compiler knows about the newly generated code and therefore can function all the expected tooling such as intellisense, code analysis,…
What to use it for?
In the less than two years that it has been out there, certain use cases have already crystallized where the developers / community are using it.
The biggest area is things around increasing application performance. Generators allow us to get rid of startup and runtime reflection. Therefore, various parsers, mappers, serializers are created, which are custom generated for your classes. There is no reflection, unnecessary memory allocation, etc. This section also includes various new DI containers that can generate the dependency tree already during the build and therefore speed up the start of your services.
Another area is simplifying your life. Getting rid of routinely written code that is not creative in any way, but it is necessary to write it because it requires, for example, some kind of framework.
As an example, I will use the INotifyPropertyChanged interface, which is well known to people who have developed WPF or Xamarin applications. These frameworks require that every property that is should be bindable must notify about the change in the set method. This is a nice thing to automate with the Source generator.
Other examples can be the generation of various proxy classes, DTO classes based on OpenApi documentation, own DSL, …
What I often do when debugging is to override the ToString method. (Who among us hasn’t done that at some point?). When the new record type was introduced, I liked that it had this method implemented so that I could see all the essentials there. It struck me as a good candidate to try C# Source Generators. So I’ll show you how to create your own generator that ToString will generate for us.
How to create your own Source Generator?
In the first step, we will create a standard .NET Standard project.
dotnet new classlib -f netstandard2.0
Subsequently, we can add our own generator to the project:
We decorate the generator with the [Generator] attribute and implement the ISourceGenerator interface. This interface is simple, it requires only two methods Initialize and Execute.
The Initialize method is called once and with it you can initialize your auxiliary structures, analyze the code, prepare what you need. In my case, I register my own ToStringReceiver with which I identify the classes marked with my [ToString] attribute.
HaveAttribute is an extension method, where we can check a class decoration with a given attribute:
In the Initialize method we register the receiver.
For the generation itself, we can you the Execute method. The simplest implementation might look like this:
In this case, I generated the ToString attribute itself, which I want the client programmer to use to decorate the class for which I generated the ToString override. It is common practice that helper / markup attributes / interfaces are generated in the resulting assembly and not referenced as a helper library.
How you generate the resulting code is entirely up to you. Sometimes a simple string is enough. Other times it is more appropriate to use StringBuilder and in specific cases it is correct to use different templating frameworks such as Scriban. In this case, StringBuilder will be completely sufficient. During generation, the technique that I first prepare the necessary data in a form that suits me and only then start the generation itself has proven itself.
The generation itself is then easy:
We will perform the generation in the Execute method.
After using the nuget package with the generator, you can view the files I generated directly in the Dependencies of the project.