如何构建依赖宿主传入配置项的Roslyn源代码生成器流水线?
Roslyn增量生成器:动态特性名称的高效处理方案
你当前尝试在Select委托中嵌套创建SyntaxProvider的方式不符合Roslyn增量生成器的设计规范,会破坏缓存机制,且无法正确展平嵌套的IncrementalValuesProvider。以下是两种可行的解决方案:
方案一:静态选项场景(性能最优)
如果特性名称是编译前就确定的静态配置(比如从项目文件的AnalyzerConfig选项读取),可以直接在初始化阶段创建多个ForAttributeWithMetadataName提供器并合并,完全保留其高性能优势:
public void Initialize(IncrementalGeneratorInitializationContext context) { // 1. 读取全局配置中的特性名称列表 var globalOptions = context.AnalyzerConfigOptionsProvider.GlobalOptions; globalOptions.TryGetValue("your_config_key", out var attributeNamesStr); var attributeNames = attributeNamesStr?.Split(',', StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty<string>(); // 2. 为每个特性名称创建独立的高效提供器 var typeProviders = new List<IncrementalValuesProvider<TypeDeclarationSyntax>>(); foreach (var attrName in attributeNames) { var provider = context.SyntaxProvider.ForAttributeWithMetadataName( attributeName: attrName, predicate: (node, _) => node is TypeDeclarationSyntax, transform: (ctx, _) => (TypeDeclarationSyntax)ctx.Node ); typeProviders.Add(provider); } // 3. 合并所有提供器的输出 IncrementalValuesProvider<TypeDeclarationSyntax> mergedProvider; if (typeProviders.Count == 0) { // 空提供器,避免后续报错 mergedProvider = context.SyntaxProvider.CreateSyntaxProvider((_,__) => false, (_,__) => null) .Where(_ => false); } else { mergedProvider = typeProviders[0]; for (int i = 1; i < typeProviders.Count; i++) { mergedProvider = mergedProvider.Concat(typeProviders[i]); } } // 4. 后续转换与代码生成(按你的需求处理TypeDeclarationSyntax) var finalDataProvider = mergedProvider.Select((typeDecl, ct) => { // 提取生成代码所需的数据,确保缓存生效 return YourTransformationLogic(typeDecl, ct); }); context.RegisterSourceOutput(finalDataProvider, (spc, data) => { // 生成代码 YourCodeGenerationLogic(spc, data); }); }
方案二:动态选项场景(兼容动态配置)
如果特性名称可能在编译过程中动态变化,只能使用CreateSyntaxProvider结合选项列表过滤,牺牲部分性能但支持动态配置:
public void Initialize(IncrementalGeneratorInitializationContext context) { // 1. 将配置转换为可缓存的特性名称集合 var targetAttributesProvider = context.AnalyzerConfigOptionsProvider .Select((optionsProvider, _) => { optionsProvider.GlobalOptions.TryGetValue("your_config_key", out var attributeNamesStr); var attributeNames = attributeNamesStr?.Split(',', StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty<string>(); return new HashSet<string>(attributeNames); }) .WithComparer(HashSet<string>.CreateSetComparer()); // 设置比较器确保缓存生效 // 2. 创建语法提供器,筛选带有目标特性的类型 var typesProvider = context.SyntaxProvider.CreateSyntaxProvider( predicate: (node, _) => node is TypeDeclarationSyntax typeDecl && typeDecl.AttributeLists.Any(), transform: (ctx, ct) => { var typeDecl = (TypeDeclarationSyntax)ctx.Node; var semanticModel = ctx.SemanticModel; var targetAttributes = targetAttributesProvider.GetValue(ct); // 检查类型是否带有目标特性之一 var hasMatchingAttribute = typeDecl.AttributeLists .SelectMany(list => list.Attributes) .Select(attr => semanticModel.GetSymbolInfo(attr).Symbol as IMethodSymbol) .Where(ctor => ctor != null) .Any(ctor => targetAttributes.Contains(ctor.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))); return hasMatchingAttribute ? typeDecl : null; }) .Where(typeDecl => typeDecl != null); // 过滤掉不匹配的类型 // 3. 后续转换与代码生成 var finalDataProvider = typesProvider.Select((typeDecl, ct) => { return YourTransformationLogic(typeDecl, ct); }); context.RegisterSourceOutput(finalDataProvider, (spc, data) => { YourCodeGenerationLogic(spc, data); }); }
关键注意事项
- 绝对不要在
IncrementalValuesProvider的委托(如Select)中创建新的SyntaxProvider,这会破坏Roslyn的缓存生命周期,导致性能下降和缓存失效。 - 静态方案的性能远高于动态方案,优先使用静态配置场景的方案。
内容的提问来源于stack exchange,提问作者MaxEd
相关产品推荐
相关产品推荐

