You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Roslyn代码生成器中获取属性的泛型参数与构造参数类型?

在Roslyn代码生成器中获取泛型特性的类型参数与构造参数

问题背景

我定义了一个泛型特性Map<A,B>和一个标注了该特性的record类型,示例代码如下:

[Map<A, B>(typeof(C), typeof(D))]
public record MyRecord{}

注:原示例中Type c, Type d不符合C#语法,修正为使用typeof传入类型参数的写法。

我已经编写了基础的Roslyn代码生成器,但不知道如何在ExecuteMapRecord方法中获取特性的泛型类型参数A,以及构造参数中的C类型。已有的生成器代码如下:

public void Initialize(IncrementalGeneratorInitializationContext context)
{
var mapSyntaxProvider = context.SyntaxProvider
    .CreateSyntaxProvider(
        predicate: (node, _) => node is RecordDeclarationSyntax { AttributeLists.Count: >= 1 },
    transform: (ctx, _) =>
    {
        return (RecordDeclarationSyntax)ctx.Node;
    })
    .Where(static rds => rds is not null);

    context.RegisterSourceOutput(
        mapSyntaxProvider,
        static (spc, syntax) => ExecuteMapRecord(spc, syntax));
}

private static void ExecuteMapRecord(
    SourceProductionContext spc, 
    RecordDeclarationSyntax syntax)
{
    ...
}

解决方案

要获取这些类型信息,不能仅依赖语法节点,必须通过**语义模型(SemanticModel)**解析真正的类型符号,具体步骤如下:

1. 修改SyntaxProvider,传递语义信息

原transform逻辑只返回语法节点,需要调整为返回包含语法节点和语义符号的结构,以便后续解析:

首先定义一个存储结果的类型(也可以用元组替代):

private record MapRecordInfo(RecordDeclarationSyntax Syntax, INamedTypeSymbol RecordSymbol);

然后更新CreateSyntaxProvider的transform方法:

var mapSyntaxProvider = context.SyntaxProvider
    .CreateSyntaxProvider(
        predicate: (node, _) => node is RecordDeclarationSyntax { AttributeLists.Count: >= 1 },
        transform: (ctx, _) =>
        {
            var recordSyntax = (RecordDeclarationSyntax)ctx.Node;
            var semanticModel = ctx.SemanticModel;
            var recordSymbol = semanticModel.GetDeclaredSymbol(recordSyntax) as INamedTypeSymbol;
            return recordSymbol is not null ? new MapRecordInfo(recordSyntax, recordSymbol) : null;
        })
    .Where(static info => info is not null);

同时更新RegisterSourceOutput的参数类型:

context.RegisterSourceOutput(
    mapSyntaxProvider,
    static (spc, info) => ExecuteMapRecord(spc, info));

2. 在ExecuteMapRecord中解析特性信息

修改ExecuteMapRecord的参数,遍历record的特性,提取目标类型:

private static void ExecuteMapRecord(
    SourceProductionContext spc, 
    MapRecordInfo recordInfo)
{
    var recordSymbol = recordInfo.RecordSymbol;
    // 替换为你实际的Map特性全限定名(注意`2`表示两个泛型参数)
    var mapAttributeSymbol = recordSymbol.Compilation.GetTypeByMetadataName("YourNamespace.Map`2");
    if (mapAttributeSymbol == null)
    {
        spc.ReportDiagnostic(Diagnostic.Create(
            new DiagnosticDescriptor(
                "MAP001", 
                "缺失Map特性", 
                "未找到Map`2特性类型", 
                "Generator", 
                DiagnosticSeverity.Error, 
                true),
            recordInfo.Syntax.GetLocation()));
        return;
    }

    // 遍历record上的所有特性,筛选出Map特性
    foreach (var attributeData in recordSymbol.GetAttributes())
    {
        if (!SymbolEqualityComparer.Default.Equals(attributeData.AttributeClass, mapAttributeSymbol))
        {
            continue;
        }

        // 获取泛型类型参数A和B
        var genericArguments = attributeData.AttributeClass.TypeArguments;
        if (genericArguments.Length >= 2)
        {
            var typeA = genericArguments[0];
            var typeB = genericArguments[1];
            // 示例:输出Type A的信息
            spc.ReportDiagnostic(Diagnostic.Create(
                new DiagnosticDescriptor(
                    "MAP002", 
                    "找到Type A", 
                    $"Type A: {typeA.ToDisplayString()}", 
                    "Generator", 
                    DiagnosticSeverity.Info, 
                    true),
                recordInfo.Syntax.GetLocation()));
        }

        // 获取构造参数中的C和D类型
        var constructorArguments = attributeData.ConstructorArguments;
        if (constructorArguments.Length >= 2)
        {
            // Type类型的构造参数,其Value对应INamedTypeSymbol
            var typeC = constructorArguments[0].Value as INamedTypeSymbol;
            var typeD = constructorArguments[1].Value as INamedTypeSymbol;
            if (typeC != null)
            {
                // 示例:输出Type C的信息
                spc.ReportDiagnostic(Diagnostic.Create(
                    new DiagnosticDescriptor(
                        "MAP003", 
                        "找到Type C", 
                        $"Type C: {typeC.ToDisplayString()}", 
                        "Generator", 
                        DiagnosticSeverity.Info, 
                        true),
                    recordInfo.Syntax.GetLocation()));
            }
        }
    }
}

3. 关键注意事项

  • 必须使用SymbolEqualityComparer.Default比较符号,不能直接用==,同一类型可能存在多个符号实例。
  • 务必替换YourNamespace.Map2`为特性的实际全限定名,包括命名空间和泛型参数数量标识。
  • 构造参数中的Type类型,其Value属性需转换为INamedTypeSymbol(引用类型场景)。
  • 使用ToDisplayString()可获取类型的可读字符串,如自定义类型名或系统类型的全限定名。

内容的提问来源于stack exchange,提问作者LosManos

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 21:54:52