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

IIncrementalGenerator在引用项目中无法生成代码的问题排查

C#增量源生成器代码未生成问题排查

我有三个C#项目,目标是通过自定义IIncrementalGenerator结合自定义特性,自动生成引用公共类的代码,参考并扩展了.NET官方教程。


公共库项目代码

public class TestRelayCommand<T> : IRelayCommand<T>
{
    #region "Constructors..."
    public TestRelayCommand(ViewModelBase viewModel, Action<T> execute)
    {
        ViewModel = viewModel;
        _command = new(execute);
    }

    public TestRelayCommand(ViewModelBase viewModel, Action<T> execute, Predicate<T> canExecute)
    {
        ViewModel = viewModel;
        _command = new(execute, canExecute);
        _command.CanExecuteChanged += _command_CanExecuteChanged;
    }
    #endregion

    #region "Properties..."
    public ViewModelBase ViewModel { get; }
    private RelayCommand<T> _command;
    public event EventHandler? CanExecuteChanged;
    #endregion

    #region "Methods..."
    public bool CanExecute(T? parameter)
    {
        return _command.CanExecute(parameter);
    }

    public bool CanExecute(object? parameter)
    {
        return _command.CanExecute(parameter);
    }

    public void Execute(object? parameter)
    {
        //Action Started
        _command.Execute(parameter);
        //Action Ended
    }

    public void Execute(T? parameter)
    {
        //Action Started
        _command.Execute(parameter);
        //Action Ended
    }

    public void NotifyCanExecuteChanged()
    {
        _command.NotifyCanExecuteChanged();
    }
    #endregion

    #region "Events..."
    private void _command_CanExecuteChanged(object? sender, EventArgs e)
    {
        CanExecuteChanged?.Invoke(this, e);
    }
    #endregion
}

[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
public class TestRelayCommandAttribute : Attribute
{
    public string? CanExecute { get; init; }
    public bool AllowConcurrentExecutions { get; init; }
    public bool FlowExceptionsToTaskScheduler { get; init; }
    public bool IncludeCancelCommand { get; init; }
}

生成器项目代码

[Generator(LanguageNames.CSharp)]
public class TestRelayCommandGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        File.WriteAllText(@"C:\Users\LMatheson\Desktop\GeneratorInitialize.txt", "Started");
        if (!Debugger.IsAttached)
        {
            Debugger.Launch();
        }
        //context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());

        var provider = context.SyntaxProvider.CreateSyntaxProvider(
            predicate: static (node, _) => node is MethodDeclarationSyntax,
            transform: static (ctx, _) => (MethodDeclarationSyntax)ctx.Node
            ).Where(m => m != null);// && m.AttributeLists.SelectMany(att => att.Attributes).Any(att => att.Name.ToString() == nameof(TestRelayCommand) || att.Name.ToString() == nameof(TestRelayCommandAttribute)));

        var compilation = context.CompilationProvider.Combine(provider.Collect());

        context.RegisterSourceOutput(compilation, Execute);
        File.WriteAllText(@"C:\Users\LMatheson\Desktop\GeneratorInitialized.txt", "Done Startup");
    }

    public void Execute(SourceProductionContext context, (Compilation Left, ImmutableArray<MethodDeclarationSyntax> Right) compilation)
    {
        File.WriteAllText(@"C:\Users\LMatheson\Desktop\GeneratorExecuting.txt", "Executing");
        foreach (var method in compilation.Right)
        {
            var semanticModel = compilation.Left.GetSemanticModel(method.SyntaxTree);
            var methodSymbol = semanticModel.GetDeclaredSymbol(method) as IMethodSymbol;

            if (methodSymbol == null || !methodSymbol.GetAttributes().Any())
                continue;

            var attributeData = methodSymbol.GetAttributes().FirstOrDefault(x => x.AttributeClass?.Name == "TestRelayCommand");
            var canExecuteArg = attributeData.NamedArguments.FirstOrDefault(arg => arg.Key == nameof(TestRelayCommandAttribute.CanExecute));
            string canExecute = canExecuteArg.Value.Value != null ? canExecuteArg.Value.Value.ToString() : null;
            if (!string.IsNullOrEmpty(canExecute)) canExecute = ", " + canExecute;

            var classDeclaration = method.FirstAncestorOrSelf<ClassDeclarationSyntax>();
            if (classDeclaration == null)
                continue;

            var namespaceDeclaration = classDeclaration.FirstAncestorOrSelf<NamespaceDeclarationSyntax>();
            var namespaceName = namespaceDeclaration?.Name.ToString();
            var className = classDeclaration.Identifier.Text;
            var methodName = method.Identifier.Text;

            var parameters = string.Join(", ", methodSymbol.Parameters.Select(p => $"{p.Type}"));
            if (!string.IsNullOrEmpty(parameters)) parameters = "<" + parameters + ">";
            //var returnType = methodSymbol.ReturnType.ToString();
            //if (returnType != "void")
            //    returnType = "<" + returnType + ">";
            //else 
            //    returnType = "";
            var source = $@"
space {namespaceName}

public partial class {className}
{{
    private WPF.UI.Analyzer.{nameof(TestRelayCommand)}{parameters} _{methodName.Substring(0, 1).ToLower()}{methodName.Substring(1)}Command;
    public WPF.UI.Analyzer.{nameof(TestRelayCommand)}{parameters} {methodName}Command => _{methodName.Substring(0, 1).ToLower()}{methodName.Substring(1)}Command ??= new(this, new({methodName}){canExecute});
}}";

            context.AddSource($"{className}_{methodName}_{nameof(TestRelayCommand)}.g.cs", SourceText.From(source, Encoding.UTF8));
            File.WriteAllText("C:\\Users\\LMatheson\\Desktop\\Generator.txt", source);
        }
    }
}

引用项目配置与代码

项目引用配置

<ProjectReference Include="..\WPF.UI.Analyzer\WPF.UI.Analyzer.csproj">
 <OutputItemType>Analyzer</OutputItemType>
 <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
</ProjectReference>

带特性的目标类

public partial class UserRoleListViewModel
{
    [TestRelayCommand]
    public override async void SelectedViewModelChanging(object args)
    {
        Debug.Print("Testing");
    }
}

问题

生成器本该创建匹配父类的分部类,包含TestRelayCommand<object>类型的字段和属性,但代码始终未生成。我已经尝试切换生成器类型、输出调试文件、配置csproj属性、测试编译等方案,均无效果。请问源生成器在引用项目中生成代码还缺少哪些必要条件?


排查与修复要点

1. 特性匹配逻辑错误

生成器里判断特性时用了x.AttributeClass?.Name == "TestRelayCommand",但实际特性类的完整名称是TestRelayCommandAttribute。C#允许在使用特性时省略Attribute后缀,但语义分析时AttributeClass.Name返回的是完整类名,必须匹配正确名称,建议用nameof避免硬编码:

var attributeData = methodSymbol.GetAttributes()
    .FirstOrDefault(x => x.AttributeClass?.Name == nameof(TestRelayCommandAttribute));

2. 语法筛选逻辑缺失

当前SyntaxProvider仅筛选所有方法,未提前过滤带目标特性的方法,导致后续处理大量无关代码,还注释掉了原本的特性筛选逻辑。应恢复并修正:

var provider = context.SyntaxProvider.CreateSyntaxProvider(
    predicate: static (node, _) => 
        node is MethodDeclarationSyntax method &&
        method.AttributeLists.SelectMany(al => al.Attributes)
                             .Any(a => a.Name.ToString() is "TestRelayCommand" or nameof(TestRelayCommandAttribute)),
    transform: static (ctx, _) => (MethodDeclarationSyntax)ctx.Node
)
.Where(m => m != null);

3. 生成代码存在语法错误

生成的代码里写了space {namespaceName},正确的命名空间声明应为namespace {namespaceName};,语法错误会导致生成的代码被编译器直接忽略。

4. 命名空间与代码结构问题

  • 生成的代码直接使用WPF.UI.Analyzer.TestRelayCommand,建议在生成代码顶部添加using WPF.UI.Analyzer;简化写法,同时确保引用项目能正确访问该命名空间。
  • 生成的分部类必须和原类在同一命名空间下,否则无法合并。

5. 文件IO可能引发异常

生成器写死了桌面路径的文件输出,若运行时无写入权限会抛出异常,中断生成流程。建议改用context.ReportDiagnostic输出调试信息,避免依赖文件系统:

context.ReportDiagnostic(Diagnostic.Create(
    new DiagnosticDescriptor("GEN001", "Generator Debug", "Found method: {0}", "Generator", DiagnosticSeverity.Info, true),
    method.GetLocation(),
    methodName));

6. 异步方法兼容性问题

目标方法是async void,虽能适配Action<T>,但异步void方法的异常无法被常规捕获,建议改为async Task并适配对应的异步命令类(如果业务需要)。


内容的提问来源于stack exchange,提问作者Lachlan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 09:42:34