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
.NET
pg

Smart Enums in .NET: Integration with Frameworks and Libraries

Learn how to seamlessly integrate Smart Enums with essential .NET frameworks and libraries. This article covers practical solutions for JSON serialization, ASP.NET Core model binding for both Minimal APIs and MVC controllers, and Entity Framework Core persistence using value converters. Discover how Thinktecture.Runtime.Extensions provides dedicated packages to eliminate integration friction and maintain type safety across your application stack.
21.09.2025
.NET
pg

Value Objects in .NET: Enhancing Business Semantics

Value objects are fundamental building blocks in Domain-Driven Design, serving far more than simple data wrappers. This article explores their strategic importance in bridging technical code and business concepts, enforcing domain rules, and fostering clearer communication with domain experts. Learn how to build robust aggregates, cultivate ubiquitous language, and encapsulate domain-specific behavior using Thinktecture.Runtime.Extensions in .NET applications.
16.09.2025
.NET
pg

Pattern Matching with Discriminated Unions in .NET

Traditional C# pattern matching with switch statements and if/else chains is error-prone and doesn't guarantee exhaustive handling of all cases. When you add new types or states, it's easy to miss updating conditional logic, leading to runtime bugs. The library Thinktecture.Runtime.Extensions solves this with built-in Switch and Map methods for discriminated unions that enforce compile-time exhaustiveness checking.
26.08.2025
.NET
pg

Value Objects in .NET: Integration with Frameworks and Libraries

Value Objects in .NET provide a structured way to improve consistency and maintainability in domain modeling. This article examines their integration with popular frameworks and libraries, highlighting best practices for seamless implementation. From working with Entity Framework to leveraging their advantages in ASP.NET, we explore how Value Objects can be effectively incorporated into various architectures. By understanding their role in framework integration, developers can optimize data handling and enhance code clarity without unnecessary complexity.
12.08.2025
.NET
pg

Smart Enums: Adding Domain Logic to Enumerations in .NET

This article builds upon the introduction of Smart Enums by exploring their powerful capability to encapsulate behavior, a significant limitation of traditional C# enums. We delve into how Thinktecture.Runtime.Extensions enables embedding domain-specific logic directly within Smart Enum definitions. This co-location of data and behavior promotes more cohesive, object-oriented, and maintainable code, moving beyond scattered switch statements and extension methods. Discover techniques to make your enumerations truly "smart" by integrating behavior directly where it belongs.
29.07.2025
.NET
pg

Discriminated Unions: Representation of Alternative Types in .NET

Representing values that may take on multiple distinct types or states is a common challenge in C#. Traditional approaches—like tuples, generics, or exceptions—often lead to clumsy and error-prone code. Discriminated unions address these issues by enabling clear, type-safe modeling of “one-of” alternatives. This article examines pitfalls of conventional patterns and introduces discriminated unions with the Thinktecture.Runtime.Extensions library, demonstrating how they enhance code safety, prevent invalid states, and improve maintainability—unlocking powerful domain modeling in .NET with minimal boilerplate.
15.07.2025