在C#增量源生成器(IIncrementalGenerator)中获取基类属性
问题描述
我在源生成器中编写了如下Execute方法,能够获取当前类的所有属性(通过x.Item1.Members.AsEnumerable()...),但无法获取基类的属性。请问如何列出基类的所有属性?
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 fullyQualifiedAggregateName) { var aggregateName = "Aggregate"; var props = x.Item1.Members.AsEnumerable().Where(o => o.IsKind(SyntaxKind.PropertyDeclaration)); var sb = new StringBuilder(); context.AddSource( $"generated_{aggregateName}_{i}.g.cs", sb.ToString()); } } }
更新
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)); } private static (ClassDeclarationSyntax, AttributeData) GetSemanticTargetForGeneration(GeneratorAttributeSyntaxContext context, CancellationToken ct) { if (context.TargetNode is not ClassDeclarationSyntax classDeclaration) { return (null, null); } AttributeData? attribute = context.Attributes.FirstOrDefault(a => a.AttributeClass?.Name == "EventApplyAttribute"); return (classDeclaration, attribute); }
解决方案
仅通过语法节点ClassDeclarationSyntax只能获取当前类定义的成员,要拿到基类属性,必须借助语义分析,通过类的符号INamedTypeSymbol遍历整个继承链。
具体实现步骤
- 获取类的语义符号
在Execute方法中,利用编译实例和语法节点获取对应的类符号:
var semanticModel = compilation.GetSemanticModel(x.Item1.SyntaxTree); if (semanticModel.GetDeclaredSymbol(x.Item1, context.CancellationToken) is not INamedTypeSymbol classSymbol) { continue; }
- 编写辅助方法遍历继承链
递归或循环遍历类的基类,收集所有非编译器自动生成的属性:
static IEnumerable<IPropertySymbol> GetAllProperties(INamedTypeSymbol typeSymbol, CancellationToken ct) { var currentType = typeSymbol; var properties = new List<IPropertySymbol>(); while (currentType != null) { // 筛选当前类的属性,排除编译器合成的隐式成员 properties.AddRange(currentType.GetMembers() .OfType<IPropertySymbol>() .Where(p => !p.IsImplicitlyDeclared)); // 移动到下一层基类 currentType = currentType.BaseType; ct.ThrowIfCancellationRequested(); } return properties; }
- 替换原有属性获取逻辑
在Execute中调用辅助方法,替换原来仅获取当前类属性的代码:
// 替换原props获取逻辑 var allProperties = GetAllProperties(classSymbol, context.CancellationToken); // 可按需筛选,比如只保留公共属性: // var publicProps = allProperties.Where(p => p.DeclaredAccessibility == Accessibility.Public);
修改后的完整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 fullyQualifiedAggregateName) { var aggregateName = "Aggregate"; // 获取语义模型与类符号 var semanticModel = compilation.GetSemanticModel(x.Item1.SyntaxTree); if (semanticModel.GetDeclaredSymbol(x.Item1, context.CancellationToken) is not INamedTypeSymbol classSymbol) { continue; } // 获取当前类及所有基类的属性 var allProperties = GetAllProperties(classSymbol, context.CancellationToken); var sb = new StringBuilder(); // 此处可使用allProperties生成目标代码 context.AddSource( $"generated_{aggregateName}_{i}.g.cs", sb.ToString()); } } } static IEnumerable<IPropertySymbol> GetAllProperties(INamedTypeSymbol typeSymbol, CancellationToken ct) { var currentType = typeSymbol; var properties = new List<IPropertySymbol>(); while (currentType != null) { properties.AddRange(currentType.GetMembers() .OfType<IPropertySymbol>() .Where(p => !p.IsImplicitlyDeclared)); currentType = currentType.BaseType; ct.ThrowIfCancellationRequested(); } return properties; }
注意事项
- 必须处理取消令牌
CancellationToken,避免生成器运行阻塞 - 可根据需求添加筛选条件,比如仅保留公共属性、排除抽象属性等
- 继承链终点为
object类,此时currentType.BaseType会返回null,循环自动终止
内容的提问来源于stack exchange,提问作者Dani
相关产品推荐
相关产品推荐

