我正在尝试构建一个增量源生成器,它生成用于依赖注入的 C# 构造函数。然而,我的生成器似乎生成了 2 个同名源
Classes.g.cs
。
该项目托管于此处。为了尝试一下:
git clone https://github.com/MintPlayer/MintPlayer.Dotnet.Tools
启动 Visual Studio,并将 SourceGenerators 项目(不是 SourceGenerators.Debug)设置为启动项目,然后按 F5。如果您通过 Visual Studio 安装程序安装了 .NET 编译器平台 SDK,这将在调试项目中的文件上运行生成器。
您会看到我在代码中添加的断点语句被击中两次,因此生成器生成此文件的次数过于频繁。
为什么该发电机运行两次?
XxxSourceGenerator 类是实际的生成器。当您键入代码时,它们将由 Visual Studio 运行。
[Generator(LanguageNames.CSharp)]
public class ClassNamesSourceGenerator : IIncrementalGenerator {
public void Initialize(IncrementalGeneratorInitializationContext context) {
var classDeclarationsProvider = context.SyntaxProvider
.CreateSyntaxProvider(
static (node, ct) => node is ClassDeclarationSyntax { } classDeclaration,
static (context, ct) => {
if (context.Node is ClassDeclarationSyntax classDeclaration &&
context.SemanticModel.GetDeclaredSymbol(classDeclaration, ct) is INamedTypeSymbol symbol) {
return new Models.ClassDeclaration { Name = symbol.Name };
} else {
return default;
}
}
)
.WithComparer(ValueComparers.ClassDeclarationValueComparer.Instance)
.Collect();
var fieldDeclarationsProvider = context.SyntaxProvider
.CreateSyntaxProvider(
static (node, ct) => node is FieldDeclarationSyntax { AttributeLists.Count: > 0 } fieldDeclaration
&& fieldDeclaration.Modifiers.Any(Microsoft.CodeAnalysis.CSharp.SyntaxKind.ReadOnlyKeyword),
static (context2, ct) => {
...
return new Models.FieldDeclaration {
Namespace = namespaceDeclaration.Name.ToString(),
FullyQualifiedClassName = classSymbol.ToDisplayString(),
ClassName = classSymbol.Name,
Name = symbol.Name,
FullyQualifiedTypeName = symbol.Type.ToDisplayString(),
Type = symbol.Type.Name,
};
}
)
.Collect();
他们首先转换来自提供者的数据,然后调用多个生产者,将符号单独转换为 C# 代码。
var classNamesSourceProvider = classDeclarationsProvider
.Combine(config)
.Select(static (p, ct) => new Producers.ClassNamesProducer(declarations: p.Left, rootNamespace: p.Right.RootNamespace!));
var classNameListSourceProvider = classDeclarationsProvider
.Combine(config)
.Select(static (p, ct) => new Producers.ClassNameListProducer(declarations: p.Left, rootNamespace: p.Right.RootNamespace!));
var fieldDeclarationSourceProvider = fieldDeclarationsProvider
.Combine(config)
.Select(static (p, ct) => new Producers.FieldNameListProducer(declarations: p.Left, rootNamespace: p.Right.RootNamespace!));
然后您需要合并所有这些生成器的输出并将您的源提供程序传递给.NET/VS
// Combine all Source Providers
var sourceProvider = classNamesSourceProvider
.Combine(classNameListSourceProvider)
.SelectMany(static (p, _) => new Producer[] { p.Left, p.Right })
.Combine(fieldDeclarationSourceProvider)
.SelectMany(static (p, _) => new Producer[] { p.Left, p.Right });
// Generate Code
context.RegisterSourceOutput(sourceProvider, static (c, g) => g?.Produce(c));
值得注意的是,如果我使用此代码片段:
var sourceProvider = fieldDeclarationSourceProvider
.Combine(classNamesSourceProvider)
.SelectMany(static (p, _) => new Producer[] { p.Left, p.Right })
.Combine(classNameListSourceProvider)
.SelectMany(static (p, _) => new Producer[] { p.Left, p.Right });
fieldDeclarations 文件仅生成一次,但 classNameList 文件生成两次,因此显然底部提供程序被触发两次
// Generates ClassNameList.g.cs twice
var sourceProvider = classNamesSourceProvider
.Combine(fieldDeclarationSourceProvider)
.SelectMany(static (p, _) => new Producer[] { p.Left, p.Right })
.Combine(classNameListSourceProvider)
.SelectMany(static (p, _) => new Producer[] { p.Left, p.Right });
// Generates FieldNameList.g.cs twice
var sourceProvider = classNamesSourceProvider
.Combine(classNameListSourceProvider)
.SelectMany(static (p, _) => new Producer[] { p.Left, p.Right })
.Combine(fieldDeclarationSourceProvider)
.SelectMany(static (p, _) => new Producer[] { p.Left, p.Right });
// Generates ClassNameList.g.cs twice
var sourceProvider = fieldDeclarationSourceProvider
.Combine(classNamesSourceProvider)
.SelectMany(static (p, _) => new Producer[] { p.Left, p.Right })
.Combine(classNameListSourceProvider)
.SelectMany(static (p, _) => new Producer[] { p.Left, p.Right });
// Generates ClassNames.g.cs twice
var sourceProvider = fieldDeclarationSourceProvider
.Combine(classNameListSourceProvider)
.SelectMany(static (p, _) => new Producer[] { p.Left, p.Right })
.Combine(classNamesSourceProvider)
.SelectMany(static (p, _) => new Producer[] { p.Left, p.Right });
// Generates ClassNames.g.cs twice
var sourceProvider = classNameListSourceProvider
.Combine(fieldDeclarationSourceProvider)
.SelectMany(static (p, _) => new Producer[] { p.Left, p.Right })
.Combine(classNamesSourceProvider)
.SelectMany(static (p, _) => new Producer[] { p.Left, p.Right });
// Generates FieldNameList.g.cs twice
var sourceProvider = classNameListSourceProvider
.Combine(classNamesSourceProvider)
.SelectMany(static (p, _) => new Producer[] { p.Left, p.Right })
.Combine(fieldDeclarationSourceProvider)
.SelectMany(static (p, _) => new Producer[] { p.Left, p.Right });
结果我不得不使用以下内容来代替
// Combine all Source Providers
var sourceProvider = classNamesSourceProvider
.Combine(fieldDeclarationSourceProvider)
.SelectMany(static (p, _) => new Producer[] { p.Left, p.Right })
.Collect()
.Combine(classNameListSourceProvider)
.SelectMany(static (p, _) => p.Left.Concat(new Producer[] { p.Right }));