在 C# 源代码生成中获取属性属性值 - IIncrementalGenerator

Get attribute property value in C# source generation - IIncrementalGenerator

提问人:Dani 提问时间:10/2/2023 最后编辑:Dani 更新时间:10/10/2023 访问量:132

问:

我有以下属性:

[AttributeUsage(AttributeTargets.Class)]
public class EventApplyAttribute : Attribute
{
    public string Aggregate { get; }

    public EventApplyAttribute(string aggregate)
    {
        Aggregate = aggregate;
    }
}

我像这样使用属性:

[EventApplyAttribute(nameof(BaseClass))]
public class Test : BaseEvent{}

我使用这个源生成器:

[Generator]
public class SourceGeneration : IIncrementalGenerator
{
    private const string EventApplyAttribute = "DomainLibrary.EventApplyAttribute";

    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        IncrementalValuesProvider<ClassDeclarationSyntax> classDeclarations = context.SyntaxProvider
            .ForAttributeWithMetadataName(
                EventApplyAttribute,
                predicate: (node, _) => node is ClassDeclarationSyntax,
                transform: (ctx, ct) => GetSemanticTargetForGeneration(ctx, ct))
            .Where(static m => m is not null);

        IncrementalValueProvider<(Compilation, ImmutableArray<ClassDeclarationSyntax>)> compilationAndClasses
        = context.CompilationProvider.Combine(classDeclarations.Collect());

        context.RegisterSourceOutput(compilationAndClasses,
            static (spc, source) => Execute(source.Item1, source.Item2, spc));

    }

    private static void Execute(Compilation compilation, ImmutableArray<ClassDeclarationSyntax> classes, SourceProductionContext context)
    {
    }

    private static ClassDeclarationSyntax? GetSemanticTargetForGeneration(GeneratorAttributeSyntaxContext context, CancellationToken ct)
    {
        string fullName = context.Attributes.First().AttributeClass.ToDisplayString();

        if (fullName == EventApplyAttribute)
        {
            if (context.TargetNode is not ClassDeclarationSyntax classDeclaration)
                return null;

            return classDeclaration;
        }

        return null;
    }
}

Execute 方法中的 classes 数组具有预期的类。如果我遍历数组,我会得到很多信息。有没有办法同时访问属性属性值。在这种情况下,它会是“BaseClass”吗?我什么都找不到...

C# c夏普源生成器

评论

0赞 V0ldek 10/2/2023
有关属性的句法信息位于以下属性中:learn.microsoft.com/en-us/dotnet/api/...AttributeList
0赞 V0ldek 10/2/2023
你能提供代码吗?否则,它不会编译,也不是 MRE。GetSemanticTargetForGeneration
0赞 Dani 10/2/2023
@V0ldek 添加了 GetSemanticTargetForGeneration

答:

0赞 Youssef13 10/2/2023 #1

该类型具有可以访问的属性。每个属性都有一个属性,其中包含您想要的内容。应在 transform 方法中将此信息提取到值相等模型中。GeneratorAttributeSyntaxContextAttributesConstructorArguments

1赞 V0ldek 10/2/2023 #2

可以使用 AttributeList 属性从对象中提取属性,该属性包含属性列表的语法。但这是乏味的,而且非常低级。ClassDeclarationSyntax

为了使它更容易并使生成器更有效率,您应该按照@Youssef13所说的去做,并将值转换为 。已经具有以下属性:GetSemanticTargetForGenerationGeneratorAttributeSyntaxContext

private static (ClassDeclarationSyntax, AttributeData) GetSemanticTargetForGeneration(GeneratorAttributeSyntaxContext context, CancellationToken ct)
{
    if (!(context.TargetNode is ClassDeclarationSyntax classDeclaration))
        return (null, null);

    AttributeData attribute = context.Attributes
        .FirstOrDefault(a => a.AttributeClass.Name == "EventApplyAttribute");

    return (classDeclaration, attribute);
}

现在,我们将返回类声明和属性数据对。您可以在方法中处理它;我不知道你的目标是什么,所以在这里我只是生成一个文件,其中包含一个带有您正在寻找的类名的注释,以表明它有效:Execute

private static void Execute(Compilation compilation, ImmutableArray<(ClassDeclarationSyntax, AttributeData)> classes, SourceProductionContext context)
{
    foreach (var (x, i) in classes.Select((x, i) => (x, i)))
    {
        TypedConstant aggregateParam = x.Item2.ConstructorArguments[0];
        
        if (aggregateParam.Kind == TypedConstantKind.Primitive &&
            aggregateParam.Value is string value)
        {
            context.AddSource(
                $"generated_{i}.g.cs",
                $"// <auto-generated/> using System; // Found param value '{value}'");
        }
    }
}

为了完整起见,您需要更改 main 方法:Initialize

public void Initialize(IncrementalGeneratorInitializationContext context)
{
    IncrementalValuesProvider<(ClassDeclarationSyntax, AttributeData)> classDeclarations = context.SyntaxProvider
        .ForAttributeWithMetadataName(
            EventApplyAttribute,
            predicate: static (node, _) => node is ClassDeclarationSyntax,
            transform: static (ctx, ct) => GetSemanticTargetForGeneration(ctx, ct))
        .Where(m => m.Item1 is not null && m.Item2 is not null);

    IncrementalValueProvider<(Compilation, ImmutableArray<(ClassDeclarationSyntax, AttributeData)>)> compilationAndClasses
    = context.CompilationProvider.Combine(classDeclarations.Collect());

    context.RegisterSourceOutput(compilationAndClasses,
        static (spc, source) => Execute(source.Item1, source.Item2, spc));
}

评论

0赞 Dani 10/3/2023
非常感谢,这很好用。找到一个像 aggregateParam.Value 这样的类的最佳方法是什么?
1赞 V0ldek 10/3/2023
该信息将在那个时候。例如,有一种按名称查询类型的方法:learn.microsoft.com/en-us/dotnet/api/...Compilation
0赞 Dani 10/10/2023
从 x.Item1 我可以获取所有属性。但我还需要基类的属性,在本例中为 BaseEvent。如何访问 BaseEvent 属性名称?