1. 项目概述:当.NET遇上Roslyn代码生成
在.NET生态中,代码生成一直是个既基础又关键的环节。传统方式如T4模板虽然能用,但总有种"隔靴搔痒"的感觉——开发体验不连贯、性能开销大、工具链支持弱。直到Roslyn编译器开放了它的API,我们终于能像手术刀般精准地操作代码了。
这个开源项目就是基于Roslyn的全新代码生成方案。它不像传统工具那样粗暴地拼接字符串,而是把代码当作结构化数据来处理。想象一下:你正在写的代码能实时分析项目结构,自动生成配套的DTO、API路由甚至单元测试,就像有个懂你心思的编程助手。我在实际项目中用它减少了40%的样板代码,特别适合需要大量重复建模的WebAPI和微服务场景。
2. 核心设计解析
2.1 Roslyn编译管道深度集成
Roslyn提供的SyntaxTree和SemanticModel是这个生成器的两大支柱。当你在VS里敲下保存键时,生成器会:
- 获取当前项目的编译上下文(Compilation)
- 分析特定标记的类/方法(通过[GenerateDto]等特性标注)
- 用SyntaxFactory构建目标代码的语法节点
- 通过增量生成器(IncrementalGenerator)输出.cs文件
关键优势在于:整个过程发生在编译初期,生成的代码会参与后续的完整编译流程。这意味着:
- 完美支持代码导航和智能提示
- 类型安全有编译器保证
- 可以基于现有代码的语义进行分析(比如自动提取接口方法)
2.2 声明式代码生成模式
项目采用了"标注即生成"的设计哲学。例如要给User类生成对应的UserDto:
[GenerateDto] public class User { public int Id { get; set; } public string Name { get; set; } }生成器会扫描所有带[GenerateDto]的类,然后:
- 解析原始类的属性签名
- 创建去除了导航属性的扁平化结构
- 自动添加DataContract序列化特性
- 在obj/Debug/net8.0/generated目录输出UserDto.g.cs
实测发现,这种模式比传统T4模板快3-5倍,因为Roslyn直接操作语法树而非文本。
3. 实战应用场景
3.1 WebAPI开发加速套件
针对ASP.NET Core项目特别提供了:
- 自动控制器生成(根据Service层接口)
- 智能FromBody/FromRoute参数推断
- 响应包装模板(统一Result 格式)
典型工作流:
[AutoController] public interface IUserService { UserDto GetUser(int id); } // 自动生成 => [ApiController] [Route("api/[controller]")] public partial class UserController : ControllerBase { [HttpGet("{id}")] public Result<UserDto> GetUser(int id) => _service.GetUser(id); }3.2 领域驱动设计支持
对于复杂领域模型,可以:
- 自动生成值对象(ValueObject)的相等性实现
- 为聚合根(AggregateRoot)生成仓储接口
- 创建领域事件的派发代码
示例配置:
[ValueObject] public record Address { public string Street { get; init; } public string City { get; init; } } // 生成Equals/GetHashCode等样板代码4. 高级定制技巧
4.1 生成策略配置
通过继承BasicGenerator可以重写关键行为:
class CustomGenerator : BasicGenerator { protected override void ProcessProperty( IPropertySymbol prop, ClassBuilder builder) { if(prop.Name.EndsWith("Id")) builder.AddAttribute("[JsonIgnore]"); } }4.2 多文件协同生成
处理复杂场景时,可以用SyntaxTree的WithFilePath控制输出位置:
context.AddSource( hintName: "SpecialCases.cs", sourceText: SyntaxFactory.ParseSyntaxTree(...) .WithFilePath("Features/Special/"));5. 性能优化实践
5.1 增量生成策略
通过实现IIncrementalGenerator接口,可以确保:
- 只有被影响的文件会重新生成
- 支持跨项目引用分析
- 缓存中间分析结果
实测在200+类的大型项目中,增量生成能将耗时从6s降至800ms。
5.2 并行处理技巧
对于独立单元的生成任务:
var compilation = context.Compilation; var symbols = GetSymbolsToProcess(compilation); Parallel.ForEach(symbols, symbol => { lock(context) { context.AddSource(/*...*/); } });注意需要处理线程竞争问题,特别是当多个生成器同时工作时。
6. 常见问题排查
6.1 生成代码不可见
检查步骤:
- 确认项目文件包含:
<PropertyGroup> <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> </PropertyGroup>- 在VS中显示所有文件
- 清理obj文件夹后重新编译
6.2 类型解析失败
当遇到"类型未找到"错误时:
- 确保相关程序集已通过[RegisterMetadata]标注
- 检查#nullable enable是否影响类型推断
- 使用compilation.GetTypeByMetadataName()时指定完整名称
7. 扩展开发指南
7.1 开发自定义生成器
推荐项目结构:
/MyGenerator ├── MyGenerator.csproj ├── MyGenerator.cs └── extensions/ └── MyExtensions.cs关键NuGet依赖:
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.7.0" PrivateAssets="all" /> <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />7.2 调试技巧
在launch.json中添加:
"args": ["--compiler-generated-files", "--debug-generators"]然后在生成器中设置调试断点:
System.Diagnostics.Debugger.Launch(); // 会弹出调试器选择窗口8. 生态整合方案
8.1 与Swagger集成
自动生成的XML注释可以通过以下方式同步到Swagger:
services.ConfigureSwaggerGen(c => { c.IncludeXmlComments(Path.Combine( AppContext.BaseDirectory, "obj/Debug/net8.0/generated/MyGenerator.xml")); });8.2 单元测试支持
为生成的代码创建验证测试:
[Test] public void Dto_HasAllProperties() { var user = new UserDto(); Assert.That(user, Has.Property(nameof(UserDto.Id))); // 使用反射验证所有属性 }我在实际项目中最喜欢的一个技巧是:通过生成器自动创建测试用的Mock数据构建器,这样能确保测试数据始终与模型保持同步。例如对于User类,生成器会产出:
public class UserBuilder { private int _id = 1; private string _name = "test"; public User Build() => new() { Id = _id, Name = _name }; public UserBuilder WithId(int id) { _id = id; return this; } }这种模式特别适合领域驱动设计项目,能显著减少测试维护成本。当模型新增属性时,构建器会自动更新(虽然需要重新编译),但至少不会漏掉任何必填字段。