Skip to content

最佳实践

📋 文档信息

难度: 🟡 中级
预计阅读时间: 25 分钟
前置知识:

  • SyntaxFactory 基础和高级
  • 代码构建模式
  • 格式化和美化

适合人群:

  • 所有代码生成器开发者
  • 需要优化代码生成性能的开发者
  • 需要调试代码生成器的开发者

📋 快速导航

章节难度阅读时间链接
推荐做法🟡8 分钟查看
反模式🟡5 分钟查看
性能优化🔴7 分钟查看
调试技巧🟡5 分钟查看

🎯 概览

本文档总结了代码生成的最佳实践、常见错误、性能优化技巧和调试方法。

本文档涵盖:

  • 推荐的代码生成做法
  • 应避免的反模式
  • 性能优化技巧
  • 调试和故障排除方法

典型应用场景:

  • 优化代码生成器性能
  • 避免常见错误
  • 调试复杂的代码生成逻辑

推荐做法

做法 1: 使用 NormalizeWhitespace 格式化代码

csharp
// ✅ 好的做法:使用 NormalizeWhitespace
public string GenerateClass(string className)
{
    var classDecl = ClassDeclaration(className)
        .AddModifiers(Token(SyntaxKind.PublicKeyword))
        .AddMembers(/* ... */);
    
    // 格式化代码
    var formatted = classDecl.NormalizeWhitespace();
    
    return formatted.ToFullString();
}

原因:自动格式化代码,保持一致的代码风格。

做法 2: 使用 using static 简化代码

csharp
// ✅ 好的做法:使用 using static
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

public class CodeGenerator
{
    public ClassDeclarationSyntax Generate()
    {
        // 不需要写 SyntaxFactory.ClassDeclaration
        return ClassDeclaration("MyClass")
            .AddModifiers(Token(SyntaxKind.PublicKeyword));
    }
}

原因:减少代码冗余,提高可读性。

做法 3: 使用辅助方法封装常见模式

csharp
// ✅ 好的做法:封装常见模式
public class CodeGenerationHelpers
{
    /// <summary>
    /// 创建公共自动属性
    /// </summary>
    public static PropertyDeclarationSyntax CreateAutoProperty(
        string typeName,
        string propertyName)
    {
        return PropertyDeclaration(
            ParseTypeName(typeName),
            Identifier(propertyName))
        .AddModifiers(Token(SyntaxKind.PublicKeyword))
        .AddAccessorListAccessors(
            AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)
                .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)),
            AccessorDeclaration(SyntaxKind.SetAccessorDeclaration)
                .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)));
    }
    
    /// <summary>
    /// 使用辅助方法
    /// </summary>
    public ClassDeclarationSyntax GenerateClass()
    {
        return ClassDeclaration("Person")
            .AddModifiers(Token(SyntaxKind.PublicKeyword))
            .AddMembers(
                CreateAutoProperty("string", "Name"),
                CreateAutoProperty("int", "Age"));
    }
}

原因:提高代码复用性和可维护性。

做法 4: 使用构建器模式组织复杂逻辑

csharp
// ✅ 好的做法:使用构建器模式
public class ClassBuilder
{
    private string _className;
    private List<PropertyDeclarationSyntax> _properties = new();
    
    public ClassBuilder WithName(string name)
    {
        _className = name;
        return this;
    }
    
    public ClassBuilder AddProperty(string type, string name)
    {
        var property = CreateAutoProperty(type, name);
        _properties.Add(property);
        return this;
    }
    
    public ClassDeclarationSyntax Build()
    {
        return ClassDeclaration(_className)
            .AddModifiers(Token(SyntaxKind.PublicKeyword))
            .AddMembers(_properties.ToArray());
    }
}

原因:使复杂的代码生成逻辑更清晰、更易维护。

做法 5: 添加 XML 文档注释

csharp
// ✅ 好的做法:添加文档注释
public ClassDeclarationSyntax GenerateDocumentedClass()
{
    var classDecl = ClassDeclaration("Person")
        .AddModifiers(Token(SyntaxKind.PublicKeyword))
        .WithLeadingTrivia(
            TriviaList(
                Comment("/// <summary>"),
                CarriageReturnLineFeed,
                Comment("/// 表示一个人的信息"),
                CarriageReturnLineFeed,
                Comment("/// </summary>"),
                CarriageReturnLineFeed));
    
    return classDecl;
}

原因:提高生成代码的可维护性和可读性。


反模式

反模式 1: 不格式化生成的代码

csharp
// ❌ 不好的做法:不格式化代码
public string GenerateClass(string className)
{
    var classDecl = ClassDeclaration(className)
        .AddModifiers(Token(SyntaxKind.PublicKeyword));
    
    // 直接返回,没有格式化
    return classDecl.ToFullString();
}
// 结果:public class MyClass{}  // 没有换行和缩进

问题:生成的代码难以阅读。

正确做法:使用 NormalizeWhitespace(参见推荐做法 1)。

反模式 2: 硬编码字符串拼接

csharp
// ❌ 不好的做法:硬编码字符串拼接
public string GenerateMethod()
{
    return "public void MyMethod() { Console.WriteLine(\"Hello\"); }";
}

问题:难以维护,容易出错,缺少语法验证。

正确做法:使用 SyntaxFactory 或 ParseStatement。

反模式 3: 忘记添加分号标记

csharp
// ❌ 不好的做法:缺少分号标记
var accessor = AccessorDeclaration(SyntaxKind.GetAccessorDeclaration);
// 结果:get  // 缺少分号

问题:生成的代码语法不正确。

正确做法

csharp
// ✅ 正确:添加分号标记
var accessor = AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)
    .WithSemicolonToken(Token(SyntaxKind.SemicolonToken));
// 结果:get;

反模式 4: 逐个添加成员

csharp
// ❌ 不好的做法:逐个添加成员
public ClassDeclarationSyntax CreateClass(List<PropertyInfo> properties)
{
    var classDecl = ClassDeclaration("MyClass")
        .AddModifiers(Token(SyntaxKind.PublicKeyword));
    
    foreach (var prop in properties)
    {
        classDecl = classDecl.AddMembers(CreateProperty(prop));  // 每次创建新对象
    }
    
    return classDecl;
}

问题:性能低下,每次都创建新对象。

正确做法

csharp
// ✅ 好:一次性添加所有成员
public ClassDeclarationSyntax CreateClass(List<PropertyInfo> properties)
{
    var members = properties
        .Select(p => CreateProperty(p))
        .ToArray();
    
    return ClassDeclaration("MyClass")
        .AddModifiers(Token(SyntaxKind.PublicKeyword))
        .AddMembers(members);  // 一次性添加
}

性能优化技巧

技巧 1: 重用 SyntaxToken

csharp
// ✅ 好:重用常用的 token
public class TokenCache
{
    private static readonly SyntaxToken PublicKeyword = 
        Token(SyntaxKind.PublicKeyword);
    private static readonly SyntaxToken PrivateKeyword = 
        Token(SyntaxKind.PrivateKeyword);
    private static readonly SyntaxToken SemicolonToken = 
        Token(SyntaxKind.SemicolonToken);
    
    public PropertyDeclarationSyntax CreateProperty(string name)
    {
        return PropertyDeclaration(
            PredefinedType(Token(SyntaxKind.StringKeyword)),
            name)
        .AddModifiers(PublicKeyword)  // 重用 token
        .AddAccessorListAccessors(
            AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)
                .WithSemicolonToken(SemicolonToken),  // 重用 token
            AccessorDeclaration(SyntaxKind.SetAccessorDeclaration)
                .WithSemicolonToken(SemicolonToken));  // 重用 token
    }
}

原因:避免重复创建相同的 token,提高性能。

技巧 2: 批量添加成员

csharp
// ✅ 好:一次性添加所有成员
public ClassDeclarationSyntax CreateClass(List<PropertyInfo> properties)
{
    var members = properties
        .Select(p => CreateProperty(p))
        .ToArray();
    
    return ClassDeclaration("MyClass")
        .AddModifiers(Token(SyntaxKind.PublicKeyword))
        .AddMembers(members);  // 一次性添加
}

// ❌ 不好:逐个添加成员
public ClassDeclarationSyntax CreateClassSlow(List<PropertyInfo> properties)
{
    var classDecl = ClassDeclaration("MyClass")
        .AddModifiers(Token(SyntaxKind.PublicKeyword));
    
    foreach (var prop in properties)
    {
        classDecl = classDecl.AddMembers(CreateProperty(prop));  // 每次创建新对象
    }
    
    return classDecl;
}

原因:减少对象创建次数,提高性能。

技巧 3: 延迟格式化

csharp
// ✅ 好:最后才格式化
public string GenerateMultipleClasses(List<string> classNames)
{
    var classes = classNames
        .Select(name => ClassDeclaration(name)
            .AddModifiers(Token(SyntaxKind.PublicKeyword)))
        .ToArray();
    
    var compilationUnit = CompilationUnit()
        .AddMembers(classes);
    
    // 只在最后格式化一次
    return compilationUnit.NormalizeWhitespace().ToFullString();
}

// ❌ 不好:每次都格式化
public string GenerateMultipleClassesSlow(List<string> classNames)
{
    var result = new StringBuilder();
    
    foreach (var name in classNames)
    {
        var classDecl = ClassDeclaration(name)
            .AddModifiers(Token(SyntaxKind.PublicKeyword))
            .NormalizeWhitespace();  // 每次都格式化
        
        result.AppendLine(classDecl.ToFullString());
    }
    
    return result.ToString();
}

原因:格式化是昂贵的操作,应该只在最后执行一次。

技巧 4: 使用 StringBuilder 拼接大量代码

csharp
// ✅ 好:对于简单的代码片段,使用 StringBuilder
public string GenerateManySimpleProperties(List<string> propertyNames)
{
    var sb = new StringBuilder();
    
    foreach (var name in propertyNames)
    {
        sb.AppendLine($"public string {name} {{ get; set; }}");
    }
    
    return sb.ToString();
}

原因:对于简单的代码片段,字符串拼接比 SyntaxFactory 更快。

技巧 5: 缓存常用的语法节点

csharp
public class SyntaxNodeCache
{
    // 缓存常用的类型
    private static readonly PredefinedTypeSyntax StringType = 
        PredefinedType(Token(SyntaxKind.StringKeyword));
    private static readonly PredefinedTypeSyntax IntType = 
        PredefinedType(Token(SyntaxKind.IntKeyword));
    private static readonly PredefinedTypeSyntax BoolType = 
        PredefinedType(Token(SyntaxKind.BoolKeyword));
    
    // 缓存常用的访问器
    private static readonly AccessorDeclarationSyntax GetAccessor = 
        AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)
            .WithSemicolonToken(Token(SyntaxKind.SemicolonToken));
    private static readonly AccessorDeclarationSyntax SetAccessor = 
        AccessorDeclaration(SyntaxKind.SetAccessorDeclaration)
            .WithSemicolonToken(Token(SyntaxKind.SemicolonToken));
    
    public PropertyDeclarationSyntax CreateStringProperty(string name)
    {
        return PropertyDeclaration(StringType, name)
            .AddModifiers(Token(SyntaxKind.PublicKeyword))
            .AddAccessorListAccessors(GetAccessor, SetAccessor);
    }
}

原因:避免重复创建相同的语法节点,提高性能。


调试技巧

技巧 1: 使用 Syntax Visualizer

Roslyn 提供了 Syntax Visualizer 工具,可以帮助你理解语法树的结构。

安装方法

  1. 在 Visual Studio 中,打开"扩展" > "管理扩展"
  2. 搜索 ".NET Compiler Platform SDK"
  3. 安装后,在"视图" > "其他窗口"中找到"Syntax Visualizer"

使用方法

  • 打开任何 C# 文件
  • 在 Syntax Visualizer 中查看语法树结构
  • 点击节点查看其类型和属性
  • 复制节点的创建代码

技巧 2: 输出生成的代码进行检查

csharp
/// <summary>
/// 调试辅助方法:输出生成的代码
/// </summary>
public class CodeGenerationDebugger
{
    public void DebugGeneratedCode(SyntaxNode node)
    {
        // 输出未格式化的代码
        Console.WriteLine("=== 未格式化 ===");
        Console.WriteLine(node.ToFullString());
        Console.WriteLine();
        
        // 输出格式化的代码
        Console.WriteLine("=== 格式化后 ===");
        Console.WriteLine(node.NormalizeWhitespace().ToFullString());
        Console.WriteLine();
        
        // 输出语法树结构
        Console.WriteLine("=== 语法树结构 ===");
        PrintSyntaxTree(node, 0);
    }
    
    private void PrintSyntaxTree(SyntaxNode node, int indent)
    {
        var indentStr = new string(' ', indent * 2);
        Console.WriteLine($"{indentStr}{node.GetType().Name}: {node.Kind()}");
        
        foreach (var child in node.ChildNodes())
        {
            PrintSyntaxTree(child, indent + 1);
        }
    }
}

技巧 3: 使用单元测试验证生成结果

csharp
using Xunit;
using Microsoft.CodeAnalysis.CSharp;

/// <summary>
/// 代码生成单元测试
/// </summary>
public class CodeGenerationTests
{
    [Fact]
    public void GenerateClass_ShouldCreateValidClass()
    {
        // Arrange
        var generator = new ClassGenerator();
        
        // Act
        var classDecl = generator.GenerateClass("Person");
        var code = classDecl.NormalizeWhitespace().ToFullString();
        
        // Assert
        Assert.Contains("public class Person", code);
        
        // 验证生成的代码可以编译
        var syntaxTree = CSharpSyntaxTree.ParseText(code);
        var compilation = CSharpCompilation.Create("Test")
            .AddSyntaxTrees(syntaxTree);
        
        var diagnostics = compilation.GetDiagnostics();
        Assert.Empty(diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error));
    }
}

技巧 4: 使用断点调试

csharp
/// <summary>
/// 在关键位置设置断点
/// </summary>
public ClassDeclarationSyntax GenerateClass()
{
    var classDecl = ClassDeclaration("MyClass");
    
    // 设置断点,检查 classDecl 的状态
    classDecl = classDecl.AddModifiers(Token(SyntaxKind.PublicKeyword));
    
    // 设置断点,检查添加修饰符后的状态
    var property = CreateProperty("Name");
    
    // 设置断点,检查属性创建结果
    classDecl = classDecl.AddMembers(property);
    
    // 设置断点,检查最终结果
    return classDecl;
}

💡 实用技巧总结

代码质量

  1. 始终格式化代码: 使用 NormalizeWhitespace()
  2. 添加文档注释: 使用 XML 文档注释
  3. 使用有意义的命名: 变量和方法名要清晰
  4. 封装常用操作: 创建辅助方法和扩展方法

性能优化

  1. 重用 token 和节点: 缓存常用的语法元素
  2. 批量操作: 一次性添加多个成员
  3. 延迟格式化: 只在最后格式化一次
  4. 选择合适的方法: 简单场景用字符串,复杂场景用 SyntaxFactory

调试和测试

  1. 使用 Syntax Visualizer: 理解语法树结构
  2. 输出中间结果: 检查生成的代码
  3. 编写单元测试: 验证生成结果
  4. 使用断点调试: 跟踪代码生成过程

⚠️ 常见错误总结

语法错误

  1. 忘记添加分号: 使用 WithSemicolonToken
  2. 缺少修饰符: 使用 AddModifiers
  3. 类型参数错误: 检查泛型类型的类型参数

格式错误

  1. 没有格式化: 使用 NormalizeWhitespace()
  2. 缩进不一致: 使用统一的缩进设置
  3. 缺少换行: 检查 trivia 设置

性能问题

  1. 重复创建对象: 重用常用的 token 和节点
  2. 逐个添加成员: 批量添加成员
  3. 频繁格式化: 只在最后格式化一次

🔗 相关文档

其他相关文档

官方资源


📚 下一步

学习完本文档后,建议:

  1. 实践应用: 在实际项目中应用这些最佳实践
  2. 性能测试: 测量和优化代码生成器的性能
  3. 持续学习: 关注 Roslyn 的最新更新和最佳实践

最后更新: 2026-02-05
文档版本: 1.0

基于 MIT 许可发布