IIncrementalGenerator在引用项目中无法生成代码的问题排查
我有三个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

