You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何无变更时增量源生成器仍被触发?如何避免不必要更新?

问题描述

以下源生成器会在初始化时创建一个新的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 13:45:24