序
据上篇专栏根据需要自动生成机械重复代码(C#、Attribute、源生成器),可以生成简单的源生成器,但每次代码一处更改时都会扫描整个语法树,开销很大,新的增量生成器[1](Incremental Generator)通过管道[2]等方式遴选需要扫描的代码,大大减少生成开销
注:增量生成器是Roslyn 4.x的新功能,对应VS17.x(即Visual Studio 2022),也就是说只有VS2022及以上的版本才可以使用
创建及使用Attribute
同上篇
创建TypeWithAttributeGenerator
using System;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Generic;
using System.Collections.Immutable;
using static SourceGenerator.Utilities;
namespace SourceGenerator;
[Generator]
public class TypeWithAttributeGenerator : IIncrementalGenerator
{
/// <summary>
/// 对拥有某attribute的type生成代码
/// </summary>
/// <param name="typeDeclarationSyntax"></param>
/// <param name="typeSymbol"></param>
/// <param name="attributeList">该类的某种Attribute</param>
/// <returns>生成的代码</returns>
private delegate string? TypeWithAttribute(TypeDeclarationSyntax typeDeclarationSyntax, INamedTypeSymbol typeSymbol, List<AttributeData> attributeList);
/// <summary>
/// 需要生成的Attribute
/// </summary>
private static readonly Dictionary<string, TypeWithAttribute> Attributes = new()
{
{ "Attributes.GenerateConstructorAttribute", TypeWithAttributeDelegates.GenerateConstructor },
{ "Attributes.LoadSaveConfigurationAttribute", TypeWithAttributeDelegates.LoadSaveConfiguration },
{ "Attributes.DependencyPropertyAttribute", TypeWithAttributeDelegates.DependencyProperty }
};
public void Initialize(IncrementalGeneratorInitializationContext context);
private static bool IsSyntaxTargetForGeneration(SyntaxNode node);
private static TypeDeclarationSyntax? GetSemanticTargetForGeneration(GeneratorSyntaxContext context);
private static void Execute(Compilation compilation, ImmutableArray<TypeDeclarationSyntax> types, SourceProductionContext context);
}
同上篇的源生成器,但继承于IIncrementalGenerator
而且这次为了提高效率、方便扩展,让所有attribute共用一个生成器,添加新的attribute时只需在Attributes上加一条键值对即可
初始化
初始化增量生成器,并指定筛选的两层,最后注册以供运行
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var typeDeclarations = context.SyntaxProvider
.CreateSyntaxProvider(
static (s, _) => IsSyntaxTargetForGeneration(s),
static (ctx, _) => GetSemanticTargetForGeneration(ctx))
.Where(static m => m is not null);
var compilationAndTypes = context.CompilationProvider.Combine(typeDeclarations.Collect());
context.RegisterSourceOutput(compilationAndTypes, static (spc, source) =>
Execute(source.Left, source.Right!, spc));
}
两次快速筛选
/// <summary>
/// 初次快速筛选(对拥有Attribute的class和record)
/// </summary>
private static bool IsSyntaxTargetForGeneration(SyntaxNode node) =>
node is TypeDeclarationSyntax { AttributeLists.Count: > 0 }
and (ClassDeclarationSyntax or RecordDeclarationSyntax);
/// <summary>
/// 获取TypeDeclarationSyntax
/// </summary>
private static TypeDeclarationSyntax? GetSemanticTargetForGeneration(GeneratorSyntaxContext context)
{
var typeDeclarationSyntax = (TypeDeclarationSyntax)context.Node;
// 不用Linq,用foreach保证速度
foreach (var attributeListSyntax in typeDeclarationSyntax.AttributeLists)
foreach (var attributeSyntax in attributeListSyntax.Attributes)
{
if (context.SemanticModel.GetSymbolInfo(attributeSyntax).Symbol is not IMethodSymbol attributeSymbol)
continue;
if (Attributes.ContainsKey(attributeSymbol.ContainingType.ToDisplayString()))
return typeDeclarationSyntax;
}
return null;
}
第一次筛选留下带有attribute的class和record类型,十分高效
第二次再选择attribute包含于Attributes里的类型
运行增量生成器
/// <summary>
/// 对获取的每个type和Attribute进行生成
/// </summary>
private static void Execute(Compilation compilation, ImmutableArray<TypeDeclarationSyntax> types, SourceProductionContext context)
{
ObjectSymbol ??= compilation.GetSpecialType(SpecialType.System_Object);
if (types.IsDefaultOrEmpty)
return;
// 遍历每个class
foreach (var typeDeclarationSyntax in types)
{
var semanticModel = compilation.GetSemanticModel(typeDeclarationSyntax.SyntaxTree);
if (semanticModel.GetDeclaredSymbol(typeDeclarationSyntax) is not INamedTypeSymbol typeSymbol)
continue;
// 同种attribute只判断一遍
var usedAttributes = new Dictionary<string, List<AttributeData>>();
// 遍历class上每个Attribute
foreach (var attribute in typeSymbol.GetAttributes())
{
var attributeName = attribute.AttributeClass!.ToDisplayString();
if (!Attributes.ContainsKey(attributeName))
continue;
if (usedAttributes.ContainsKey(attributeName))
usedAttributes[attributeName].Add(attribute);
else
usedAttributes[attributeName] = new List<AttributeData> { attribute };
}
foreach (var usedAttribute in usedAttributes)
if (Attributes[usedAttribute.Key](typeDeclarationSyntax, typeSymbol, usedAttribute.Value) is { } source)
context.AddSource(
// 不能重名
$"{typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted))}_{usedAttribute.Key}.g.cs",
source);
}
}
对每个type的每种attribute进行处理,调用Attributes的值(处理函数)
处理函数
public static string? DependencyProperty(TypeDeclarationSyntax typeDeclaration, INamedTypeSymbol typeSymbol, List<AttributeData> attributeList);
对比上篇的处理函数,函数声明稍有变化,但完全等同:
原参数GeneratorExecutionContext context被取消,AddSource操作改为用string返回,并在Execute函数中实现
原参数INamedTypeSymbol attributeType是用来判断是否是指定的attribute,通过以下代码实现:
attribute => SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, attributeType)
现在传入数组List<AttributeData> attributeList,直接传入所有的指定类型的Attribute,可替代该冗长语句
其他两个参数同上篇专栏
函数体内容与上篇内容几乎一致,故不再赘述

引用图片

参考资料
[1] GitHub-IncrementalGenerators(https://github.com/dotnet/roslyn/blob/main/docs/features/incremental-generators.md)
[2] Creating a source generator(https://andrewlock.net/series/creating-a-source-generator)