.NET Tools
Essential productivity kit for .NET and game developers
Source Generators in .NET 5 with ReSharper
One of the most exciting features in .NET 5 and C# 9 are source generators. They enable us to hook into the compilation pipeline that is executed for our projects, analyze source files that are to be compiled, and provide additional source code for our final assembly. In other words, we can take advantage of compile-time metaprogramming!
Are you performing expensive reflection operations? Do you need to write a lot of boilerplate code like route mappings or builder methods? Source generators can make our lives a lot easier with these and many more scenarios that are yet to be discovered!
Made an experimental #dotnetcore source generator for generating typed x:Name directives for #Avalonia #Xaml files. Should be easier to use #ReactiveUI code-behind #bindings in @AvaloniaUI apps now. The generator uses #XamlX internally 🚀 https://t.co/NpSh9iJIuW pic.twitter.com/lgOpUma4lw
— Artyom V. Gorchakov (@worldbeaterdev) October 26, 2020
A source generator must be attributed with the Generator
attribute and implement the ISourceGenerator
interface. More detailed examples can be found in the source generators cookbook. In its most rudimentary form, we’d start with the following:
[Generator]
public class MyGenerator : ISourceGenerator
{
public void Initialize(GeneratorInitializationContext context)
{
}
public void Execute(GeneratorExecutionContext context)
{
}
}
Internally, Roslyn treats source generators like analyzers. What’s interesting is that they’re also packaged the same way.
A source generator has full access to the syntax trees and the semantic model, i.e. the Compilation
object. A common approach for source generators is to use partial classes and marker attributes, however, we’re by no means limited to this! It is important to note though, that source generators won’t stack. Each will see the original set of source files, so code generators are oblivious of other code generators. Also, they can’t change existing code, so they’re not a replacement for AOP frameworks like Fody or PostSharp.
One of the crucial aspects of source generators is the IDE integration. As we write code, we should have immediate access to the code generated by source generators. For instance, when we use the Data Builder Generator by Martin Ullrich, the generated builder methods should be accessible from our project right away.
With ReSharper 2020.3 EAP 5, the additional code is read from the Visual Studio workspace and then included as in-memory source files into our code model. This allows ReSharper’s code completion to function as usual:
If you’re curious about the generated code, you can always navigate to a generated symbol, i.e. the in-memory document! Since source generators can output multiple additional source files, you might also consider writing the complete generated code to disk. This is possible by setting two properties in your C# project file (requires .NET SDK 5.0.0-rc.2 or higher):
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> <CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GeneratedFiles</CompilerGeneratedFilesOutputPath>
In preparation for this blog post, I did a pair-streaming session with my friend Martin, in which we explored setting up source generators in more detail. At the end of the session we’ve created an initial draft of a generator that creates mocks and the SUT for unit tests. Feel free to reach out if you have any questions, and let us know how you feel about source generators as a consumer!
Happy generating! And try out the latest EAP’s of ReSharper and Rider.