为何无变更时增量源生成器仍被触发?如何避免不必要更新?
问题描述
以下源生成器会在初始化时创建一个新的record类型,随后在另一步骤中选择该类型的符号,生成带时间戳的占位文件。由于底层record类型仅在初始化时生成一次且后续无变更,预期后续转换步骤仅执行一次,但在Rider 2022.3.1中,每次输入操作都会重新生成test2.g.cs并更新时间戳。请问该现象的原因是什么?如何避免这些不必要的更新?
public class TestGenerator : IIncrementalGenerator { public void Initialize(IncrementalGeneratorInitializationContext context) { // Create test1.g.cs on initialization context.RegisterPostInitializationOutput(context => context.AddSource("test1.g.cs", "public record TestRecord();")); // Find the type symbol for the previously generated record, and generate test2.g.cs based on this context.RegisterSourceOutput( context.SyntaxProvider.CreateSyntaxProvider( static (context, _) => context is RecordDeclarationSyntax s && s.Identifier.Text.Equals("TestRecord"), static (context, _) => context.SemanticModel.GetDeclaredSymbol(context.Node) as INamedTypeSymbol ).Where(x => x is not null), (context, symbol) => context.AddSource("test2.g.gs", $"// Found: {symbol is not null} at {DateTime.UtcNow}") ); } }
原因分析
- 语法节点的不稳定标识:你通过
SyntaxProvider匹配的是初始化阶段生成的RecordDeclarationSyntax节点,但每次IDE输入触发增量生成时,该语法节点会被重新创建——即使内容完全一致,它的引用标识(如哈希值)也会变化。增量生成系统会将其判定为新输入,进而重新执行后续生成逻辑。 - 符号无稳定化追踪:当前实现未对获取到的
INamedTypeSymbol做稳定化处理,增量生成系统无法判断符号是否与之前的实例一致,导致每次都会触发RegisterSourceOutput执行。 - Rider的增量触发逻辑差异:Rider 2022.3.1的增量生成触发策略比原生Roslyn更敏感,即使输入无实质变更,也可能因内部缓存机制的差异导致生成步骤重复执行。
解决方法
核心思路是让增量生成系统识别输入的稳定性,具体可通过两种方式处理:
方式一:直接依赖初始化输出(推荐)
既然TestRecord是初始化阶段固定生成的,后续生成逻辑无需通过语法查找符号,直接绑定初始化阶段的稳定输入即可:
public class TestGenerator : IIncrementalGenerator { public void Initialize(IncrementalGeneratorInitializationContext context) { // 初始化阶段生成TestRecord context.RegisterPostInitializationOutput(ctx => ctx.AddSource("test1.g.cs", "public record TestRecord();") ); // 绑定初始化阶段的稳定输入,仅执行一次生成 var stableInitSignal = context.GeneratePostInitializationOutput(() => Unit.Value); context.RegisterSourceOutput(stableInitSignal, (ctx, _) => ctx.AddSource("test2.g.cs", $"// Found: true at {DateTime.UtcNow}") ); } }
方式二:为符号添加稳定化比较器
如果实际场景中必须依赖符号(比如符号可能来自用户代码),可以为INamedTypeSymbol添加自定义相等比较器,基于符号的完全限定名等稳定属性判断一致性:
public class TestGenerator : IIncrementalGenerator { public void Initialize(IncrementalGeneratorInitializationContext context) { context.RegisterPostInitializationOutput(ctx => ctx.AddSource("test1.g.cs", "public record TestRecord();") ); // 为符号添加稳定化比较器 var testRecordSymbol = context.SyntaxProvider .CreateSyntaxProvider( static (node, _) => node is RecordDeclarationSyntax s && s.Identifier.Text == "TestRecord", static (ctx, _) => ctx.SemanticModel.GetDeclaredSymbol(ctx.Node) as INamedTypeSymbol ) .Where(s => s != null) .WithComparer(new NamedTypeSymbolEqualityComparer()); context.RegisterSourceOutput(testRecordSymbol, (ctx, symbol) => ctx.AddSource("test2.g.cs", $"// Found: {symbol != null} at {DateTime.UtcNow}") ); } // 自定义符号相等比较器 private class NamedTypeSymbolEqualityComparer : IEqualityComparer<INamedTypeSymbol> { public bool Equals(INamedTypeSymbol? x, INamedTypeSymbol? y) { if (x == null || y == null) return x == y; return x.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) == y.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); } public int GetHashCode(INamedTypeSymbol obj) { return obj.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat).GetHashCode(); } } }
内容的提问来源于stack exchange,提问作者Bogey
相关产品推荐
相关产品推荐

