专栏/源生成器(二):高效轻量的增量生成器

源生成器(二):高效轻量的增量生成器

2022年04月03日 08:34--浏览 · --点赞 · --评论
粉丝:122文章:26

据上篇专栏根据需要自动生成机械重复代码(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,可替代该冗长语句

其他两个参数同上篇专栏

函数体内容与上篇内容几乎一致,故不再赘述

引用图片

[わいっしゅ] ID = 96178543

参考资料

[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)

投诉或建议