Roslyn Source Generators如何获取引用项目语义模型与源码
Roslyn源生成器默认运行在目标项目的编译管线中,默认仅能直接访问当前目标项目(即示例中的TargetProject)的语法树与编译对象,被引用的RefProjectA、RefProjectB默认以编译后元数据的形式存在,需要根据你的需求选择对应实现方式:
获取引用项目的语义元数据(无需原始源码场景)
如果你的逻辑只需要获取引用项目的类型定义、成员签名、属性标注等编译后语义信息,不需要读取原始源码,不需要额外配置,直接通过现有编译对象即可获取:
- 遍历
context.Compilation.SourceModule.ReferencedAssemblySymbols拿到所有被引用的程序集符号 - 通过程序集名称过滤出RefProjectA、RefProjectB对应的
IAssemblySymbol实例 - 拿到程序集符号后可直接通过符号API访问所有公开/内部(需要配置InternalsVisibleTo给源生成器)的类型、成员信息,所有语义查询逻辑和操作目标项目本地符号完全一致
注意:该方式仅能访问编译后保留在元数据中的信息,无法获取原始源码文本、注释、预处理指令、未编译进元数据的源码结构,性能最优,适合仅需要类型元数据的场景。
获取引用项目原始源码与对应语义模型(需要源码分析场景)
如果你需要分析RefProjectA的原始语法结构、源码文本,必须先修改项目配置传递源码,再在生成器中解析生成语义模型:
第一步:配置引用项目,向源生成器传递源码
在RefProjectA、RefProjectB的csproj文件中添加以下配置,将项目源码作为附加文件传递给上游项目的源生成器:
<ItemGroup> <!-- 将当前项目所有C#源码文件标记为附加文件,随项目引用传递 --> <AdditionalFiles Include="**/*.cs" /> <!-- 给附加文件打来源标记,方便生成器识别文件所属项目 --> <CompilerVisibleItemMetadata Include="AdditionalFiles" MetadataName="SourceProject" Value="$(AssemblyName)" /> </ItemGroup>
请确保TargetProject对RefProjectA、RefProjectB的引用为ProjectReference类型,直接引用编译后dll无法触发附加文件的传递逻辑。
第二步:在生成器中解析源码生成语义模型
在生成器执行逻辑中按以下步骤处理:
- 遍历
context.AdditionalFiles集合,通过之前配置的SourceProject元数据,过滤出所有属于RefProjectA、RefProjectB的.cs源码文件 - 对每个过滤出的文件调用
GetText()获取原始源码文本,调用CSharpSyntaxTree.ParseText()将文本解析为Roslyn语法树 - 基于TargetProject的原有Compilation对象,调用
AddReferences()补充RefProjectA、RefProjectB的依赖程序集,再调用AddSyntaxTrees()把刚才解析出的引用项目语法树加入,得到新的Compilation实例 - 对新的Compilation实例调用
GetSemanticModel(syntaxTree),即可拿到对应引用项目语法树的语义模型,所有语义查询行为和本地项目的语义模型完全一致
踩坑提示:不要直接用TargetProject原始的Compilation对象为引用项目的语法树生成语义模型,会出现元数据不匹配、符号解析失败的问题,必须将引用项目的语法树和其依赖加入到同一个Compilation实例中再生成语义模型。
增量生成器优化写法
如果你使用的是IIncrementalGenerator,不要每次编译全量解析所有附加文件,可以通过增量提供器缓存解析结果,避免拖慢编译速度:
public void Initialize(IncrementalGeneratorInitializationContext context) { // 过滤出属于RefProjectA的所有源码附加文件 IncrementalValuesProvider<SyntaxTree> refProjectASyntaxTrees = context.AdditionalFilesProvider .Combine(context.AnalyzerConfigOptionsProvider) .Where(pair => { var (file, configProvider) = pair; if (!file.Path.EndsWith(".cs")) return false; return configProvider.GetOptions(file) .TryGetValue("build_metadata.AdditionalFiles.SourceProject", out var projectName) && projectName == "RefProjectA"; }) .Select((pair, token) => { var (file, _) = pair; var sourceText = file.GetText(token); return CSharpSyntaxTree.ParseText(sourceText, cancellationToken: token); }); // 将解析出的语法树和主编译对象组合,后续做增量语义分析即可 var compilationWithRefA = context.CompilationProvider.Combine(refProjectASyntaxTrees.Collect()); }
内容的提问来源于stack exchange,提问作者Eike S

