1. 为什么需要C#代码生成技术?
在传统开发流程中,我们经常需要重复编写大量相似代码。以数据库操作为例,每个实体类都需要配套的CRUD方法,这些代码结构高度相似但细节不同。我曾参与过一个ERP系统开发,其中包含200多个实体类,手动编写这些重复代码不仅耗时,还容易出错。
代码生成技术通过模板化方式自动生成重复性代码,将开发效率提升3-5倍。根据我的实测数据,一个标准的订单管理模块:
- 手动开发需要8小时
- 使用代码生成仅需1.5小时(包含模板调整时间)
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 主流C#代码生成方案对比
2.1 T4模板引擎
T4(Text Template Transformation Toolkit)是Visual Studio内置的代码生成工具。我在多个项目中使用过T4,它的优势在于:
xml复制<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Data" #>
<#@ import namespace="System.Data" #>
<#
// 从数据库读取表结构
var tables = GetDatabaseTables();
#>
// 自动生成的实体类
<# foreach(var table in tables) { #>
public class <#= table.Name #>
{
<# foreach(var column in table.Columns) { #>
public <#= column.DataType #> <#= column.Name #> { get; set; }
<# } #>
}
<# } #>
实际经验:T4模板在VS2019后性能提升明显,但调试仍然不便。建议配合Tangible T4 Editor插件使用。
2.2 Roslyn代码分析器
Roslyn提供了强大的代码分析生成能力。我在一个AOP项目中用它动态生成代理类:
csharp复制// 创建语法树
var classDeclaration = SyntaxFactory.ClassDeclaration("UserProxy")
.AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword))
.AddBaseListTypes(SyntaxFactory.SimpleBaseType(
SyntaxFactory.ParseTypeName("IUserService")));
// 生成方法
var method = SyntaxFactory.MethodDeclaration(
SyntaxFactory.PredefinedType(
SyntaxFactory.Token(SyntaxKind.StringKeyword)),
"GetUserName")
.AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword))
.AddBodyStatements(
SyntaxFactory.ParseStatement("Console.WriteLine(\"Before\");"),
SyntaxFactory.ParseStatement("return _target.GetUserName();"),
SyntaxFactory.ParseStatement("Console.WriteLine(\"After\");"));
2.3 第三方工具对比
| 工具名称 | 学习曲线 | 生成速度 | 定制能力 | 适用场景 |
|---|---|---|---|---|
| CodeSmith | 中等 | 快 | 强 | 企业级代码生成 |
| NSwag | 低 | 极快 | 中 | API客户端生成 |
| AutoMapper | 低 | 快 | 中 | 对象映射代码 |
| EF Power Tools | 中等 | 快 | 强 | 数据库反向工程 |
3. 实战:构建自己的代码生成器
3.1 设计代码生成架构
我推荐的分层架构:
- 数据源层:数据库、Excel、JSON等
- 模板层:Razor/T4/Handlebars模板
- 引擎层:模板解析和渲染
- 输出层:文件生成/内存编译
3.2 数据库驱动代码生成实例
以生成EF Core上下文为例:
csharp复制public void GenerateDbContext(DatabaseSchema schema, string outputPath)
{
var template = @"
using Microsoft.EntityFrameworkCore;
namespace {{Namespace}}
{
public class {{ContextName}} : DbContext
{
{% for table in Tables %}
public DbSet<{{table.EntityName}}> {{table.Name}} { get; set; }
{% endfor %}
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options.UseSqlServer(@""{{ConnectionString}}"");
}
}";
var engine = new HandlebarsEngine();
var result = engine.Render(template, new {
Namespace = "MyProject.Data",
ContextName = "AppDbContext",
ConnectionString = "...",
Tables = schema.Tables
});
File.WriteAllText(Path.Combine(outputPath, "AppDbContext.cs"), result);
}
3.3 动态编译生成代码
对于需要即时执行的场景,可以使用CSharpScript:
csharp复制var options = ScriptOptions.Default
.AddReferences(typeof(DateTime).Assembly)
.AddImports("System");
var result = await CSharpScript.EvaluateAsync<int>("1 + 2", options);
4. 高级技巧与优化方案
4.1 模板设计模式
我总结的模板最佳实践:
- 将模板拆分为header/body/footer三部分
- 使用partial模板复用公共片段
- 添加条件生成逻辑:
handlebars复制{{#if addLogging}}
// 日志代码
{{/if}}
4.2 性能优化方案
在大规模生成时(如生成1000+个类):
- 并行化模板渲染
- 缓存编译后的模板
- 增量生成机制
测试数据对比:
| 方案 | 100个类耗时 | 1000个类耗时 |
|---|---|---|
| 单线程 | 1.2s | 12.8s |
| 并行(4核) | 0.4s | 3.2s |
| 模板预编译 | 0.3s | 2.1s |
5. 常见问题解决方案
5.1 类型加载异常处理
当遇到"无法加载一个或多个请求的类型"错误时:
- 检查LoaderExceptions属性获取详细信息
- 确保所有依赖项已正确部署
- 使用Assembly.LoadFrom替代Assembly.LoadFile
5.2 多线程代码生成
在生成器中使用多线程时:
csharp复制Parallel.ForEach(templates, template => {
try {
GenerateCode(template);
}
catch (Exception ex) {
// 确保异常不会导致进程崩溃
ErrorHandler.Log(ex);
}
});
5.3 与外部系统集成
与PLC通信代码生成示例:
csharp复制// 生成汇川PLC通信代码
public string GenerateInovancePLCInterface(PlcSpec spec)
{
return $@"
public class {spec.DeviceName}PlcClient
{{
private readonly SerialPort _port = new SerialPort("{spec.PortName}", {spec.BaudRate});
public void Connect()
{{
_port.Open();
}}
{GenerateRegisterAccessors(spec.Registers)}
}}";
}
6. 现代开发中的代码生成
6.1 结合Source Generators
C# 9引入的Source Generators可以在编译时生成代码:
csharp复制[Generator]
public class DtoGenerator : ISourceGenerator
{
public void Execute(GeneratorExecutionContext context)
{
var syntaxTrees = context.Compilation.SyntaxTrees;
// 分析语法树并生成代码
context.AddSource("GeneratedDto.cs", generatedCode);
}
}
6.2 领域特定语言(DSL)应用
设计简单的DSL生成业务逻辑:
code复制rule "库存预警"
when 库存量 < 安全库存
then 发送通知("库存不足")
转换器生成C#代码:
csharp复制public class InventoryRule
{
public void Check(Inventory inventory)
{
if(inventory.Quantity < inventory.SafetyStock)
Notifier.Send("库存不足");
}
}
在Visual Studio中,可以通过自定义项目模板快速创建符合企业规范的项目结构。我常用的模板包括:
- 标准Web API模板(集成Swagger/Serilog)
- 微服务模板(包含健康检查/配置中心)
- 桌面应用模板(MVVM架构)
创建自定义模板步骤:
- 开发标准项目作为模板原型
- 使用
dotnet new -i安装模板 - 添加可替换参数(如
$safeprojectname$)
实际案例:通过模板将新项目创建时间从2小时缩短到5分钟,同时确保所有项目遵循统一规范。
