如何在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
相关产品推荐
相关产品推荐

