Incremental Roslyn Source Generators: High-Level API – ForAttributeWithMetadataName – Part 8

With the version 4.3.1 of Microsoft.CodeAnalysis.* Roslyn provides a new high-level API - the method "ForAttributeWithMetadataName". Although it is just 1 method, still, it addresses one of the biggest performance issue with Source Generators.

In diesem Artikel:

pg
Pawel Gerr ist Architekt und Consultant bei Thinktecture. Er hat sich auf .NET Core Backends spezialisiert und kennt Entity Framework von vorne bis hinten.

More information about the Smart Enums and the source code can be found on GitHub:

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<DemoEnumInfo>() 
                             .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); 
   } 
				
			
The method 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!

Kostenloser
Newsletter

Aktuelle Artikel, Screencasts, Webinare und Interviews unserer Experten für Sie

Verpassen Sie keine Inhalte zu Angular, .NET Core, Blazor, Azure und Kubernetes und melden Sie sich zu unserem kostenlosen monatlichen Dev-Newsletter an.

Newsletter Anmeldung
Diese Artikel könnten Sie interessieren
Database Access with Sessions
.NET
KP-round

Data Access in .NET Native AOT with Sessions

.NET 8 brings Native AOT to ASP.NET Core, but many frameworks and libraries rely on unbound reflection internally and thus cannot support this scenario yet. This is true for ORMs, too: EF Core and Dapper will only bring full support for Native AOT in later releases. In this post, we will implement a database access layer with Sessions using the Humble Object pattern to get a similar developer experience. We will use Npgsql as a plain ADO.NET provider targeting PostgreSQL.
15.11.2023
Old computer with native code
.NET
KP-round

Native AOT with ASP.NET Core – Overview

Originally introduced in .NET 7, Native AOT can be used with ASP.NET Core in the upcoming .NET 8 release. In this post, we look at the benefits and drawbacks from a general perspective and perform measurements to quantify the improvements on different platforms.
02.11.2023
.NET
KP-round

Optimize ASP.NET Core memory with DATAS

.NET 8 introduces a new Garbage Collector feature called DATAS for Server GC mode - let's make some benchmarks and check how it fits into the big picture.
09.10.2023
AI
favicon

Integrating AI Power into Your .NET Applications with the Semantic Kernel Toolkit – an Early View

With the rise of powerful AI models and services, questions come up on how to integrate those into our applications and make reasonable use of them. While other languages like Python already have popular and feature-rich libraries like LangChain, we are missing these in .NET and C#. But there is a new kid on the block that might change this situation. Welcome Semantic Kernel by Microsoft!
03.05.2023
.NET
sg

.NET 7 Performance: Regular Expressions – Part 2

There is this popular quote by Jamie Zawinski: Some people, when confronted with a problem, think "I know, I'll use regular expressions." Now they have two problems."

In this second article of our short performance series, we want to look at the latter one of those problems.
25.04.2023
.NET
sg

.NET 7 Performance: Introduction and Runtime Optimizations – Part 1

.NET 7 is fast. Superfast. All the teams at Microsoft working on .NET are keen to improve the performance and do so every year with each new .NET release. Though this time the achievements are really impressive. In this series of short articles, we want to explore some of the most significant performance updates in .NET and look at how that may affect our own projects. This first article is taking a deep look under the hood of the compiler and the runtime to look for some remarkably interesting and significant updates.
28.03.2023