能否通过C# IIncrementalGenerator向其他项目生成代码?
跨项目源代码生成问题
我正在编写一个实现IIncrementalGenerator的源代码生成器,需要读取一个所有属性均为常量的struct,示例如下:
public struct MyStruct { public const string Prop1 = "PROP1"; // More properties }
我已经通过以下代码,在该struct所在的同一项目中成功生成代码:
[Generator] public class MyGenerator : IIncrementalGenerator { public void Initialize(IncrementalGeneratorInitializationContext context) { var structs = context.SyntaxProvider .CreateSyntaxProvider( (s, _) => s is StructDeclarationSyntax sds, (c, _) => { return (c.Node is StructDeclarationSyntax sds && sds.Identifier.Text == "MyStruct") ? sds : null; } ) .Where(x => x != null) .Collect(); context.RegisterSourceOutput(structs, GenerateSource); } private void GenerateSource(SourceProductionContext context, ImmutableArray<StructDeclarationSyntax?> structs) { string myCode = // my generated code context.AddSource($"MyCode.g.cs", myCode); } }
现在我希望向另一个项目生成额外代码,请问通过IncrementalGeneratorInitializationContext和SourceProductionContext是否可行?
我的思路:
- 是否应使用
AdditionalTextsProvider? - 是否需要在另一个项目中编写新生成器,并访问原项目中的
MyStruct?如果是,该如何实现?
解决方案
核心结论
直接通过当前生成器向其他项目输出代码是不可行的,因为源代码生成器的作用域被限制在其运行的项目内——SourceProductionContext.AddSource只能将代码添加到当前生成器所在的项目(或引用该生成器的项目),无法跨项目输出。
针对思路的具体解答
1. AdditionalTextsProvider是否适用?
AdditionalTextsProvider用于读取项目中的额外文件(如JSON、XML等),但它无法让生成器直接访问另一个项目中的编译符号(比如MyStruct的常量),所以这个方案不适用你的需求。
2. 跨项目生成的正确实现方式
你需要在目标项目中引用生成器,并让生成器能够读取原项目中MyStruct的元数据,具体步骤如下:
步骤1:确保原项目的MyStruct能被目标项目访问
- 将定义
MyStruct的项目设为类库,让目标项目通过项目引用或NuGet包引用它。 - 注意:生成器只能读取编译后的元数据,所以
MyStruct必须是公共可见的(public修饰符),且其常量属性也需是公共的。
步骤2:修改生成器,通过语义模型读取元数据
原生成器是通过语法树(StructDeclarationSyntax)读取MyStruct,这种方式只能访问当前项目内的语法节点。跨项目时需要改用语义分析,读取已编译的类型符号:
[Generator] public class MyGenerator : IIncrementalGenerator { public void Initialize(IncrementalGeneratorInitializationContext context) { // 注册编译提供器,获取当前项目的编译上下文 var compilationProvider = context.CompilationProvider; context.RegisterSourceOutput(compilationProvider, (ctx, compilation) => { // 查找原项目中的MyStruct类型符号(替换为实际命名空间) var myStructType = compilation.GetTypeByMetadataName("YourNamespace.MyStruct"); if (myStructType == null) return; // 遍历所有公共常量属性 var constantProperties = myStructType.GetMembers() .OfType<IFieldSymbol>() .Where(f => f.IsConst && f.IsPublic); // 生成目标项目所需的代码 string generatedCode = GenerateCrossProjectCode(constantProperties); ctx.AddSource("CrossProjectCode.g.cs", generatedCode); }); } private string GenerateCrossProjectCode(IEnumerable<IFieldSymbol> constants) { // 编写你的代码生成逻辑,示例为生成常量类 var codeBuilder = new StringBuilder(); codeBuilder.AppendLine("namespace CrossProjectGenerated;"); codeBuilder.AppendLine("public static class MyConstants"); codeBuilder.AppendLine("{"); foreach (var constant in constants) { codeBuilder.AppendLine($" public const string {constant.Name} = \"{constant.ConstantValue}\";"); } codeBuilder.AppendLine("}"); return codeBuilder.ToString(); } }
步骤3:在目标项目中配置生成器
- 将生成器项目打包为NuGet包,或通过项目引用添加到目标项目。
- 确保目标项目引用了包含
MyStruct的原项目类库。 - 当目标项目编译时,生成器会读取
MyStruct的元数据,在目标项目内生成代码。
关键注意事项
- 生成器运行在目标项目的编译阶段,只能访问目标项目引用的已编译程序集,无法直接读取原项目的源代码。
- 如果
MyStruct的常量值发生变化,需要重新编译原项目,目标项目才能获取到更新后的元数据。
内容的提问来源于stack exchange,提问作者Frank Modica
相关产品推荐
相关产品推荐

