使用IIncrementalGenerator无法访问目标项目的项目引用依赖问题
跨项目使用IIncrementalGenerator实现RabbitMQ消息消费注册
问题背景
项目结构如下:
Consumer/ Program.cs MessageHandler.cs ... SourceGenerator/ RabbitMQGenerator.cs Domain/ Messages/ TestCommand.cs TestEvent.cs
- Consumer项目引用Domain项目,并通过项目引用将SourceGenerator作为Analyzer依赖
- 基于
IIncrementalGenerator实现的源代码生成器核心功能:- 查找所有标记特定特性的消息处理器
- 查找所有标记特定特性的消息类型
- 为消息处理器中的方法匹配对应消息类型(消息类型定义RabbitMQ队列),自动生成消费注册代码
当前问题:
- 消息类型放在Consumer项目内时,生成器正常工作
- 消息类型移到Domain项目后,生成器无法检测到这些类型
- 若给Domain项目添加SourceGenerator依赖,会导致生成器在两个项目各运行一次,不符合预期
本质需求:源代码生成器能否从多个项目/程序集中分别获取数据并整合处理?如何实现?
项目文件内容
Consumer项目文件
<Project Sdk="Microsoft.NET.Sdk.Worker"> <PropertyGroup> <TargetFramework>net7.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> </PropertyGroup> <ItemGroup> <!-- Other nuget dependencies --> </ItemGroup> <ItemGroup> <ProjectReference Include="..\Domain\Domain.csproj" /> <ProjectReference Include="..\SourceGenerator\SourceGenerator.csproj" ReferenceOutputAssembly="false" OutputItemType="Analyzer" /> </ItemGroup> </Project>
Domain项目文件
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netstandard2.0</TargetFramework> </PropertyGroup> <ItemGroup> <!-- Other nuget dependencies --> </ItemGroup> </Project>
Source Generator项目文件
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netstandard2.0</TargetFramework> <LangVersion>10.0</LangVersion> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> <CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath> <EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules> <IsRoslynComponent>true</IsRoslynComponent> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.6.0" /> <None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" /> </ItemGroup> </Project>
代码示例
MessageHandler.cs(Consumer项目)
[EntityHandler] public class MessageConsumer { [MessageHandler(typeof(TestCommand))] public Task OnTestCommand(TestCommand command) { // 业务逻辑 } }
TestCommand.cs(Domain项目)
[MessageType("RabbitMQ.TestCommand")] public class TestCommand : IRabbitMqCommand { // 消息字段 }
解决方案
核心原理
IIncrementalGenerator支持分析当前编译上下文的所有引用程序集,生成器运行在Consumer项目的编译阶段时,会自动加载Domain项目的编译输出(程序集),通过Roslyn API可从引用程序集中提取标记特性的类型,无需给Domain项目添加生成器依赖。
具体实现步骤
收集跨项目的类型与方法数据
在生成器的Initialize方法中,通过IncrementalValuesProvider收集当前编译及引用程序集中的目标类型与方法:public void Initialize(IncrementalGeneratorInitializationContext context) { // 收集所有标记MessageType特性的类型(含引用程序集) var messageTypes = context.SyntaxProvider .CreateSyntaxProvider( predicate: (node, _) => node is ClassDeclarationSyntax, transform: (ctx, _) => ExtractMessageTypeInfo(ctx.SemanticModel, (ClassDeclarationSyntax)ctx.Node)) .Where(info => info != null); // 收集所有标记EntityHandler的类中的MessageHandler方法 var handlerMethods = context.SyntaxProvider .CreateSyntaxProvider( predicate: (node, _) => node is MethodDeclarationSyntax, transform: (ctx, _) => ExtractHandlerMethodInfo(ctx.SemanticModel, (MethodDeclarationSyntax)ctx.Node)) .Where(info => info != null); // 合并数据源并生成注册代码 var combinedData = messageTypes.Collect().Combine(handlerMethods.Collect()); context.RegisterSourceOutput(combinedData, (spc, data) => { var messageTypeList = data.Left; var handlerMethodList = data.Right; GenerateRabbitMQRegistrationCode(spc, messageTypeList, handlerMethodList); }); }通过语义模型提取引用程序集的特性信息
利用SemanticModel获取类型符号,判断是否标记目标特性并提取参数:private MessageTypeInfo? ExtractMessageTypeInfo(SemanticModel semanticModel, ClassDeclarationSyntax classDecl) { var typeSymbol = semanticModel.GetDeclaredSymbol(classDecl); if (typeSymbol == null) return null; // 检查是否存在MessageType特性 var messageTypeAttr = typeSymbol.GetAttributes() .FirstOrDefault(attr => attr.AttributeClass?.Name.Equals(nameof(MessageTypeAttribute), StringComparison.Ordinal) == true); if (messageTypeAttr == null) return null; // 提取队列名称参数 var queueName = messageTypeAttr.ConstructorArguments.FirstOrDefault().Value as string; if (string.IsNullOrEmpty(queueName)) return null; return new MessageTypeInfo { TypeFullName = typeSymbol.ToDisplayString(), QueueName = queueName }; }确保特性类型可被生成器识别
将标记用的特性(EntityHandlerAttribute、MessageHandlerAttribute、MessageTypeAttribute)放在Domain项目中,SourceGenerator项目引用Domain项目,确保生成器能识别这些特性的符号。
关键注意事项
- 仅在Consumer项目中添加SourceGenerator作为Analyzer依赖,避免生成器在多个项目重复运行
- 特性类需设为
public,并添加[AttributeUsage]标记,确保语义模型能正确识别 - 处理引用程序集时,必须通过
Symbol而非SyntaxNode获取信息(引用程序集无源代码,仅含编译后的符号)
内容的提问来源于stack exchange,提问作者Lucas
相关产品推荐
相关产品推荐

