Skip to content

代码生成 API 基础

⏱️ 5-10 分钟 | 📚 基础级别

🎯 学习目标

完成本指南后,你将能够:

  • [ ] 理解代码生成的两种方式(字符串 vs SyntaxFactory)
  • [ ] 使用字符串生成简单代码
  • [ ] 使用 AddSource() 添加生成的代码
  • [ ] 格式化生成的代码
  • [ ] 生成常见的代码结构

📖 核心概念

什么是代码生成?

代码生成是 Source Generator 的核心功能,它允许你在编译时动态创建 C# 代码。生成的代码会被添加到编译中,就像手写的代码一样。

两种代码生成方式

1. 基于字符串的生成(推荐用于简单代码)

  • 直接编写 C# 代码字符串
  • 简单直观,易于理解
  • 适合生成简单的类、方法

2. 使用 SyntaxFactory(用于复杂代码)

  • 使用 Roslyn API 构建语法树
  • 类型安全,不易出错
  • 适合生成复杂的代码结构

何时使用哪种方式?

  • 简单代码(< 50 行): 使用字符串
  • 复杂代码(> 50 行): 使用 SyntaxFactory
  • 动态结构: 使用 SyntaxFactory
  • 模板代码: 使用字符串

🔧 常用方法

方法 1:使用 AddSource() 添加代码

用途: 将生成的代码添加到编译中

何时使用: 每次生成代码时都需要使用

示例:

csharp
using Microsoft.CodeAnalysis;

[Generator]
public class SimpleCodeGenerator : ISourceGenerator
{
    public void Execute(GeneratorExecutionContext context)
    {
        // 生成代码字符串
        var sourceCode = @"
namespace Generated
{
    public class HelloWorld
    {
        public string GetMessage()
        {
            return ""Hello from generator!"";
        }
    }
}";
        
        // 添加到编译
        // 参数1: 文件名(必须以 .g.cs 结尾)
        // 参数2: 源代码字符串
        context.AddSource("HelloWorld.g.cs", sourceCode);
    }
    
    public void Initialize(GeneratorInitializationContext context) { }
}

关键要点:

  • 文件名必须以 .g.cs 结尾(表示生成的代码)
  • 文件名必须唯一,避免冲突
  • 源代码必须是有效的 C# 代码

方法 2:生成简单的类

用途: 使用字符串生成基本的类结构

何时使用: 需要生成简单的 POCO 类、DTO 等

示例:

csharp
using Microsoft.CodeAnalysis;

[Generator]
public class ClassGenerator : ISourceGenerator
{
    public void Execute(GeneratorExecutionContext context)
    {
        // 定义类信息
        var className = "Person";
        var namespaceName = "MyApp.Models";
        
        // 生成类代码
        var code = $@"
namespace {namespaceName}
{{
    public partial class {className}
    {{
        public string Name {{ get; set; }}
        public int Age {{ get; set; }}
        
        public string GetInfo()
        {{
            return $""{{Name}} is {{Age}} years old"";
        }}
    }}
}}";
        
        context.AddSource($"{className}.g.cs", code);
    }
    
    public void Initialize(GeneratorInitializationContext context) { }
}

关键要点:

  • 使用字符串插值 $ 动态生成内容
  • 使用双花括号转义花括号
  • 建议生成 partial 类,方便用户扩展

方法 3:生成带属性的类

用途: 根据数据动态生成多个属性

何时使用: 需要根据配置或元数据生成属性时

示例:

csharp
using Microsoft.CodeAnalysis;
using System.Linq;

[Generator]
public class PropertyGenerator : ISourceGenerator
{
    public void Execute(GeneratorExecutionContext context)
    {
        // 定义属性列表
        var properties = new[]
        {
            ("Name", "string"),
            ("Age", "int"),
            ("Email", "string"),
            ("IsActive", "bool")
        };
        
        // 生成属性代码
        var propertiesCode = string.Join("\n    ", 
            properties.Select(p => 
                $"public {p.Item2} {p.Item1} {{ get; set; }}"));
        
        // 生成完整类
        var code = $@"
namespace Generated
{{
    public partial class User
    {{
        {propertiesCode}
    }}
}}";
        
        context.AddSource("User.g.cs", code);
    }
    
    public void Initialize(GeneratorInitializationContext context) { }
}

关键要点:

  • 使用 LINQ 动态生成重复结构
  • 使用 string.Join() 组合多个代码片段
  • 注意缩进和格式

方法 4:生成方法

用途: 生成方法代码

何时使用: 需要生成辅助方法、扩展方法等

示例:

csharp
using Microsoft.CodeAnalysis;

[Generator]
public class MethodGenerator : ISourceGenerator
{
    public void Execute(GeneratorExecutionContext context)
    {
        var className = "Calculator";
        
        // 生成多个方法
        var code = $@"
namespace Generated
{{
    public static class {className}
    {{
        public static int Add(int a, int b)
        {{
            return a + b;
        }}
        
        public static int Subtract(int a, int b)
        {{
            return a - b;
        }}
        
        public static int Multiply(int a, int b)
        {{
            return a * b;
        }}
        
        public static double Divide(int a, int b)
        {{
            if (b == 0)
                throw new System.DivideByZeroException();
            return (double)a / b;
        }}
    }}
}}";
        
        context.AddSource($"{className}.g.cs", code);
    }
    
    public void Initialize(GeneratorInitializationContext context) { }
}

关键要点:

  • 方法可以包含完整的逻辑
  • 注意异常处理
  • 使用 static 类生成工具方法

方法 5:添加 using 语句

用途: 在生成的代码中添加命名空间引用

何时使用: 生成的代码需要使用外部类型时

示例:

csharp
using Microsoft.CodeAnalysis;
using System.Linq;

[Generator]
public class UsingGenerator : ISourceGenerator
{
    public void Execute(GeneratorExecutionContext context)
    {
        // 定义需要的命名空间
        var usings = new[]
        {
            "System",
            "System.Collections.Generic",
            "System.Linq",
            "System.Text"
        };
        
        // 生成 using 语句
        var usingStatements = string.Join("\n", 
            usings.Select(u => $"using {u};"));
        
        // 生成完整代码
        var code = $@"
{usingStatements}

namespace Generated
{{
    public class DataProcessor
    {{
        public List<string> ProcessData(string[] input)
        {{
            return input
                .Where(s => !string.IsNullOrEmpty(s))
                .Select(s => s.ToUpper())
                .ToList();
        }}
    }}
}}";
        
        context.AddSource("DataProcessor.g.cs", code);
    }
    
    public void Initialize(GeneratorInitializationContext context) { }
}

关键要点:

  • using 语句放在文件开头
  • 只添加必要的命名空间
  • 使用 string.Join() 生成多个 using

💡 实际示例

示例 1:生成 DTO 类

场景: 为数据库实体生成对应的 DTO 类

解决方案:

csharp
using Microsoft.CodeAnalysis;

[Generator]
public class DtoGenerator : ISourceGenerator
{
    public void Execute(GeneratorExecutionContext context)
    {
        // 假设我们要为 User 实体生成 UserDto
        var entityName = "User";
        var dtoName = $"{entityName}Dto";
        
        var code = $@"
using System;

namespace MyApp.Dtos
{{
    /// <summary>
    /// 为 {entityName} 实体生成的 DTO
    /// </summary>
    public class {dtoName}
    {{
        public int Id {{ get; set; }}
        public string Name {{ get; set; }}
        public string Email {{ get; set; }}
        public DateTime CreatedAt {{ get; set; }}
        
        /// <summary>
        /// 从实体创建 DTO
        /// </summary>
        public static {dtoName} FromEntity({entityName} entity)
        {{
            return new {dtoName}
            {{
                Id = entity.Id,
                Name = entity.Name,
                Email = entity.Email,
                CreatedAt = entity.CreatedAt
            }};
        }}
    }}
}}";
        
        context.AddSource($"{dtoName}.g.cs", code);
    }
    
    public void Initialize(GeneratorInitializationContext context) { }
}

说明:

  • 生成了完整的 DTO 类
  • 包含 XML 文档注释
  • 提供了从实体转换的静态方法

示例 2:生成扩展方法

场景: 为字符串类型生成常用的扩展方法

解决方案:

csharp
using Microsoft.CodeAnalysis;

[Generator]
public class ExtensionMethodGenerator : ISourceGenerator
{
    public void Execute(GeneratorExecutionContext context)
    {
        var code = @"
using System;

namespace Generated.Extensions
{
    public static class StringExtensions
    {
        /// <summary>
        /// 检查字符串是否为空或仅包含空白字符
        /// </summary>
        public static bool IsNullOrWhiteSpace(this string value)
        {
            return string.IsNullOrWhiteSpace(value);
        }
        
        /// <summary>
        /// 截断字符串到指定长度
        /// </summary>
        public static string Truncate(this string value, int maxLength)
        {
            if (string.IsNullOrEmpty(value))
                return value;
            
            return value.Length <= maxLength 
                ? value 
                : value.Substring(0, maxLength) + ""..."";
        }
        
        /// <summary>
        /// 反转字符串
        /// </summary>
        public static string Reverse(this string value)
        {
            if (string.IsNullOrEmpty(value))
                return value;
            
            var chars = value.ToCharArray();
            Array.Reverse(chars);
            return new string(chars);
        }
    }
}";
        
        context.AddSource("StringExtensions.g.cs", code);
    }
    
    public void Initialize(GeneratorInitializationContext context) { }
}

说明:

  • 生成了多个实用的扩展方法
  • 包含完整的错误处理
  • 添加了 XML 文档注释

⏭️ 下一步

🔗 相关资源

📚 在学习路径中使用

此 API 在以下步骤中使用:

基于 MIT 许可发布