如何在编译时动态生成C#类?寻求IDE可识别的最优方案
最优技术方案:.NET源代码生成器(Source Generator)
核心原理
源代码生成器是.NET 5及以上版本官方提供的编译时扩展能力,它能在项目编译过程中分析现有代码的语法和语义信息,动态生成符合需求的C#源代码文件。这些文件会被直接纳入编译流程,IDE(如Visual Studio、Rider)能实时识别并提供智能提示、代码跳转等功能——完全等同于手写代码的体验,正好解决你提到的Fody注入代码无法被IDE识别的问题。
具体实现步骤
1. 创建源代码生成器项目
新建一个类库项目,目标框架选择netstandard2.0(确保兼容绝大多数.NET版本),并添加以下NuGet包:
Microsoft.CodeAnalysis.CSharp Microsoft.CodeAnalysis.Analyzers
2. 实现生成器逻辑
编写实现ISourceGenerator接口的生成器类,通过语法接收器收集目标实体类,再根据实体类的元数据生成Provider代码:
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System.Text; [Generator] public class EntityProviderGenerator : ISourceGenerator { public void Initialize(GeneratorInitializationContext context) { // 注册语法接收器,用于筛选目标实体类 context.RegisterForSyntaxNotifications(() => new EntitySyntaxReceiver()); } public void Execute(GeneratorExecutionContext context) { var receiver = (EntitySyntaxReceiver)context.SyntaxReceiver!; foreach (var classDecl in receiver.TargetEntities) { // 获取实体类的语义符号(包含命名空间、属性等元数据) var classSymbol = context.Compilation.GetSemanticModel(classDecl.SyntaxTree) .GetDeclaredSymbol(classDecl) as INamedTypeSymbol; if (classSymbol == null) continue; // 生成Provider类的源代码 var source = GenerateProviderCode(classSymbol); // 将生成的代码添加到编译流程,文件名后缀为.g.cs便于识别 context.AddSource($"{classSymbol.Name}Provider.g.cs", SourceText.From(source, Encoding.UTF8)); } } private string GenerateProviderCode(INamedTypeSymbol entitySymbol) { var entityName = entitySymbol.Name; var providerName = $"{entityName}Provider"; var ns = entitySymbol.ContainingNamespace.ToString(); // 可扩展逻辑:比如遍历实体所有属性生成赋值代码,适配复杂需求 return $$""" namespace {{ns}}; public class {{providerName}} { public {{entityName}} GetEntity(object content) => new {{entityName}} { Content = content }; } """; } } // 语法接收器:筛选需要生成Provider的实体类(这里以类名以Entity结尾为例,也可自定义属性标记) public class EntitySyntaxReceiver : ISyntaxReceiver { public List<ClassDeclarationSyntax> TargetEntities { get; } = new(); public void OnVisitSyntaxNode(SyntaxNode node) { if (node is ClassDeclarationSyntax classDecl && classDecl.Identifier.Text.EndsWith("Entity")) { TargetEntities.Add(classDecl); } } }
3. 在主项目中引用生成器
修改主项目的.csproj文件,添加对生成器项目的引用,注意设置OutputItemType为Analyzer,ReferenceOutputAssembly为false:
<ProjectReference Include="..\EntityProviderGenerator\EntityProviderGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
4. 验证效果
编译主项目后,生成的SomeProvider类会出现在obj/Debug/netX/generated/EntityProviderGenerator目录下,IDE会自动识别该类,支持智能提示和代码跳转;当SomeEntity类修改时,生成器会自动重新生成对应的Provider代码。
方案对比与优势
- Fody:基于IL注入实现,仅在编译后修改程序集,IDE无法识别注入的代码,不适合需要IDE感知的代码生成场景;
- T4模板:需要手动触发生成,编译时不会自动同步更新,集成度远低于源代码生成器;
- 自定义脚本/工具:需要手动执行或配置构建事件,无法实时同步代码变化,维护成本高。
源代码生成器的核心优势:
- IDE实时感知:生成的代码完全融入IDE的代码分析流程,和手写代码无差别;
- 编译时同步:实体类修改后自动重新生成Provider,确保代码一致性;
- 类型安全:基于语义分析生成代码,能精准获取实体类的元数据(属性、方法、命名空间等),避免字符串拼接的错误;
- 增量生成:仅在目标实体类变化时重新生成对应的代码,编译效率更高。
内容的提问来源于stack exchange,提问作者Snowind
相关产品推荐
相关产品推荐

