Article series
- Code sharing of the future
- Better experience through Roslyn Analyzers and Code Fixes
- Testing Source Generators, Roslyn Analyzers and Code Fixes
- Increasing Performance through Harnessing of the Memoization
- Adapt Code Generation Based on Project Dependencies
- Using 3rd-Party Libraries
- Using Additional Files
- High-Level API – ForAttributeWithMetadataName ⬅
- Reduction of resource consumption in IDE
- Configuration
- Logging
More information about the Smart Enums and the source code can be found on GitHub:
- Smart Enums
- Source Code (see commits starting with message “Part 8”)
In the previous article we talked about how to use a JSON file as an input for code generation. Today we will take a small detour to look at the new high-level API – the method ForAttributeWithMetadataName
– which brings a major performance boost to a lot of Source Generators.
Alias-Support or Performance? Both!
In part 4 we had to decide whether we want better performance but at the cost of losing support for C# aliases or vice versa. The new high-level API provided by Roslyn team addresses this issue so we can have both – aliases and performance.
ForAttributeWithMetadataName
The new method ForAttributeWithMetadataName
is an alternative to CreateSyntaxProvider
if the Source Generator is using an attribute to find code fragments (i.e. syntax nodes) of interest.
The DemoSourceGenerator
, implemented and refined in this series of articles, uses a custom EnumGenerationAttribute
to turn classes into Smart Enums. The method ForAttributeWithMetadataName
is a perfect match for this job. The required changes are mainly deleting some code.
First, replace CreateSyntaxProvider
with ForAttributeWithMetadataName
and pass the fully-qualified name of the attribute, i.e. DemoLibrary.EnumGenerationAttribute
. The method GetEnumInfoOrNull
doesn’t return null anymore, so it is renamed to GetEnumInfo
.
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// OLD
var enumTypes = context.SyntaxProvider
.CreateSyntaxProvider(CouldBeEnumerationAsync, GetEnumInfoOrNull)
.Where(type => type is not null)!
.Collect()
.SelectMany((enumInfos, _) => enumInfos.Distinct());
// NEW
var enumTypes = context.SyntaxProvider
.ForAttributeWithMetadataName("DemoLibrary.EnumGenerationAttribute",
CouldBeEnumerationAsync,
GetEnumInfo)
.Collect()
.SelectMany((enumInfos, _) => enumInfos.Distinct());
The method CouldBeEnumerationAsync
doesn’t have to check for the attribute anymore because it is done by ForAttributeWithMetadataName
. Furthermore, the passed SyntaxNode
is not the attribute but a node to which the attribute is attached to, like a class declaration. Roslyn uses the term “target” node in this API.
// OLD
private static bool CouldBeEnumerationAsync(
SyntaxNode syntaxNode,
CancellationToken cancellationToken)
{
if (syntaxNode is not AttributeSyntax attribute)
return false;
var name = ExtractName(attribute.Name);
if (name is not ("EnumGeneration" or "EnumGenerationAttribute"))
return false;
return attribute.Parent?.Parent is ClassDeclarationSyntax classDeclaration &&
IsPartial(classDeclaration);
}
// NEW
private static bool CouldBeEnumerationAsync(
SyntaxNode syntaxNode,
CancellationToken cancellationToken)
{
return syntaxNode is ClassDeclarationSyntax classDeclaration &&
IsPartial(classDeclaration);
}
GetEnumInfo(OrNull)
gets a GeneratorAttributeSyntaxContext
containing not just the TargetNode
but TargetSymbol
as well, i.e. direct usage of the SemanticModel
is not necessary anymore. Additionally, the context provides the attributes (context.Attributes
) as well, which is guaranteed to contain the attribute we are looking for.
// OLD
private static DemoEnumInfo? GetEnumInfoOrNull(
GeneratorSyntaxContext context,
CancellationToken cancellationToken)
{
var classDeclaration = (ClassDeclarationSyntax)context.Node.Parent!.Parent!;
var type = ModelExtensions.GetDeclaredSymbol(context.SemanticModel, classDeclaration) as ITypeSymbol;
return type is null || !IsEnumeration(type) ? null : new DemoEnumInfo(type);
}
// NEW
private static DemoEnumInfo GetEnumInfo(
GeneratorAttributeSyntaxContext context,
CancellationToken cancellationToken)
{
var type = (INamedTypeSymbol)context.TargetSymbol;
var enumInfo = new DemoEnumInfo(type);
return enumInfo;
}
Summary
The high-level API provided by ForAttributeWithMetadataName
is the first, but probably not the last one. I don’t see any reason for not using this method, so, start using it now!