我需要编写一个源生成器,扫描项目中的一组或另一组类(用不同的属性标记),然后从它们生成相同的代码(第二组属性是“兼容性”选项)。当然,我可以只找到所有类型声明,然后使用选项过滤它们,但微软表示 ForAttributeWithMetadataName 比 CreateSyntaxProvider 快得多,并且将所有过滤卸载到 RegisterSourceOutput 对缓存不利。
到目前为止,我遇到的唯一解决方案(或者更确切地说,解决方案的一部分)是使用这样的嵌套值提供程序:
var optionsPipeline = context.AnalyzerConfigOptionsProvider.Select((optionsProvider, _) =>
{
// Transform option value into a list of attribute names
})
.SelectMany((attributeNames,_) => attributeNames);
// At this point, we have an IncrementalValuesProvider<string>
var mainPipeline = optionsPipeline.Select((attributeName, _) =>
{
var typesDeclarations = context.SyntaxProvider.ForAttributeWithMetadataName(
attributeName,
predicate: { ... }
transform: { ... }
);
return typesDeclaration;
});
// At this point, mainPipeline is IncrementalValuesProvider<IncrementalValuesProvider<TypeDeclarationSyntax>>
我需要对所选的 TypeDeclarationSytnax 应用更多转换,以便缓存实际工作,并提取实际源生成所需的数据,但这在这里无关紧要。
现在,我不明白下一步是什么。理想情况下,我想通过附加每个提供者的输出将
IncrementalValuesProvider<IncrementalValuesProvider<TypeDeclarationSyntax>>
转换为 IncrementalValuesProvider<TypeDeclarationSyntax>
,然后调用 RegisterSourceOutput,但我不明白这是否有可能实现这一点。
我可以按原样在
mainPipeline
上调用 RegisterSourceOutput,但我什至不确定它是否合法(RegisterSourceOutput 的操作将收到 和 ImmutableArray<IncrementalValuesProvider<TypeDeclarationSyntax>>
,而且我不确定此时它能用它做任何事情).
那么,是否有人知道我是否可以完成这项工作,或者是否有另一种方法可以构建依赖于选项的管道而不将过滤卸载到 RegisterSourceOutput?或者这是一个愚蠢的差事?
这个精确的解决方案无法工作,但我的建议是为您需要的每个属性调用
ForAttributeWithMetadataName
(没有好的方法可以用循环来做到这一点,因为您需要稍后将它们全部组合起来,您只需必须为每个属性编写一个调用),然后将“兼容性”属性与提供程序结合起来,以确定是否启用兼容性选项,如下所示:
var compatibilityEnabled =
context.AnalyzerConfigOptionsProvider
.Select((o, _) =>
o.GlobalOptions.TryGetValue("yourconfigoption", out var opt) && bool.TryParse(opt, out var optBool) ? optBool : false
);
var nonCompatibilityAttribute =
context.SyntaxProvider.ForAttributeWithMetadataName(
"FirstAttribute",
predicate: { ... },
transform: { ... }
);
var compatibilityAttribute =
context.SyntaxProvider.ForAttributeWithMetadataName(
"SecondAttribute",
predicate: { ... },
transform: { ... }
)
.Combine(compatibilityEnabled)
.Where(pair => pair.Right)
.Select((pair, _) => pair.Left);
// finally
context.RegisterSourceOutput(nonCompatibilityAttribute.Collect().Combine(compatibilityAttribute.Collect()).Combine(...), { ... });