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

使用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项目添加生成器依赖。

具体实现步骤

  1. 收集跨项目的类型与方法数据
    在生成器的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);
        });
    }
    
  2. 通过语义模型提取引用程序集的特性信息
    利用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
        };
    }
    
  3. 确保特性类型可被生成器识别
    将标记用的特性(EntityHandlerAttribute、MessageHandlerAttribute、MessageTypeAttribute)放在Domain项目中,SourceGenerator项目引用Domain项目,确保生成器能识别这些特性的符号。

关键注意事项

  • 仅在Consumer项目中添加SourceGenerator作为Analyzer依赖,避免生成器在多个项目重复运行
  • 特性类需设为public,并添加[AttributeUsage]标记,确保语义模型能正确识别
  • 处理引用程序集时,必须通过Symbol而非SyntaxNode获取信息(引用程序集无源代码,仅含编译后的符号)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 05:02:12