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 this article:

pg
Pawel Gerr is architect and consultant at Thinktecture. He focuses on backends with .NET Core and knows Entity Framework inside out.

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!

Free
Newsletter

Current articles, screencasts and interviews by our experts

Don’t miss any content on Angular, .NET Core, Blazor, Azure, and Kubernetes and sign up for our free monthly dev newsletter.

EN Newsletter Anmeldung (#7)
Related Articles
.NET
pg
A key aspect of adopting any new pattern is understanding how it interacts with the surrounding application infrastructure. When using Discriminated Unions, questions arise: How can a Result union be serialized to JSON? How can an OrderState union be persisted using Entity Framework Core? This article explores practical integration strategies with common .NET frameworks.
02.11.2025
.NET
pg
While basic value objects solve primitive obsession, complex domain requirements need sophisticated modeling techniques. This article explores advanced patterns using Thinktecture.Runtime.Extensions to tackle real-world scenarios: open-ended dates for employment contracts, composite file identifiers across storage systems, recurring anniversaries without year components, and geographical jurisdictions using discriminated unions.
19.10.2025
.NET
pg
Domain models often involve concepts that exist in multiple distinct states or variations. Traditional approaches using enums and nullable properties can lead to invalid states and scattered logic. This article explores how discriminated unions provide a structured, type-safe way to model domain variants in .NET, aligning perfectly with Domain-Driven Design principles while enforcing invariants at the type level.
06.10.2025