Skip to content

API 快速入门指南

5 分钟快速上手 Roslyn Source Generator APIs

🎯 5 分钟快速开始

欢迎!本指南将帮助你快速理解和使用最常见的 Source Generator APIs。每个示例都是独立完整的,可以直接使用。

1. 生成简单代码

最直接的代码生成方式是使用 AddSource() 进行基于字符串的生成。

csharp
using Microsoft.CodeAnalysis;

[Generator]
public class SimpleGenerator : ISourceGenerator
{
    public void Execute(GeneratorExecutionContext context)
    {
        // 生成一个简单的类
        var sourceCode = @"
namespace Generated
{
    public class HelloWorld
    {
        public string GetMessage() => ""Hello from generator!"";
    }
}";
        
        // 将生成的代码添加到编译中
        context.AddSource("HelloWorld.g.cs", sourceCode);
    }
    
    public void Initialize(GeneratorInitializationContext context) { }
}

关键要点:

  • 使用 context.AddSource() 添加生成的代码
  • 第一个参数是文件名(必须以 .g.cs 结尾)
  • 第二个参数是源代码字符串

2. 查找带有特定特性的类

使用 ForAttributeWithMetadataName() 进行高效的基于特性的代码生成(仅限增量生成器)。

csharp
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;

[Generator]
public class AttributeGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // 查找所有带有 [GenerateDto] 特性的类
        var classDeclarations = context.SyntaxProvider
            .ForAttributeWithMetadataName(
                "GenerateDtoAttribute",
                predicate: (node, _) => node is ClassDeclarationSyntax,
                transform: (ctx, _) => GetClassInfo(ctx))
            .Where(info => info is not null);
        
        // 为每个类生成代码
        context.RegisterSourceOutput(classDeclarations, 
            (spc, classInfo) => GenerateDto(spc, classInfo));
    }
    
    private static ClassInfo? GetClassInfo(GeneratorAttributeSyntaxContext context)
    {
        var classDecl = (ClassDeclarationSyntax)context.TargetNode;
        return new ClassInfo(classDecl.Identifier.Text);
    }
}

record ClassInfo(string Name);

关键要点:

  • ForAttributeWithMetadataName() 是查找带特性类型的最快方式
  • 使用 predicate 提前过滤语法节点
  • 使用 transform 提取所需信息

3. 报告诊断信息

报告错误、警告或信息消息,帮助用户修复问题。

csharp
using Microsoft.CodeAnalysis;

public class DiagnosticGenerator : ISourceGenerator
{
    // 定义诊断描述符
    private static readonly DiagnosticDescriptor MustBePartialRule = new(
        id: "GEN001",
        title: "类必须是 partial",
        messageFormat: "类 '{0}' 必须声明为 partial 才能使用代码生成",
        category: "Generator",
        DiagnosticSeverity.Error,
        isEnabledByDefault: true);
    
    public void Execute(GeneratorExecutionContext context)
    {
        // 检查类是否为 partial
        bool isPartial = CheckIfPartial(context);
        
        if (!isPartial)
        {
            // 报告错误
            var diagnostic = Diagnostic.Create(
                MustBePartialRule,
                Location.None,
                "MyClass");
            
            context.ReportDiagnostic(diagnostic);
        }
    }
    
    private bool CheckIfPartial(GeneratorExecutionContext context) => true;
    public void Initialize(GeneratorInitializationContext context) { }
}

关键要点:

  • 使用唯一 ID 和消息创建 DiagnosticDescriptor
  • 使用 Diagnostic.Create() 创建诊断实例
  • 调用 context.ReportDiagnostic() 报告到 IDE

4. 使用语义模型获取类型信息

使用语义模型访问类型信息、符号和语义分析。

csharp
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Linq;

public class SemanticAnalysisExample
{
    public void AnalyzeClass(GeneratorExecutionContext context)
    {
        var compilation = context.Compilation;
        var syntaxTree = compilation.SyntaxTrees.First();
        var semanticModel = compilation.GetSemanticModel(syntaxTree);
        
        var root = syntaxTree.GetRoot();
        var classDecl = root.DescendantNodes()
            .OfType<ClassDeclarationSyntax>()
            .First();
        
        // 获取类的符号
        var classSymbol = semanticModel.GetDeclaredSymbol(classDecl) 
            as INamedTypeSymbol;
        
        if (classSymbol != null)
        {
            // 获取所有属性
            var properties = classSymbol.GetMembers()
                .OfType<IPropertySymbol>()
                .Where(p => p.DeclaredAccessibility == Accessibility.Public);
            
            // 使用属性信息进行生成
            foreach (var prop in properties)
            {
                var propName = prop.Name;
                var propType = prop.Type.ToDisplayString();
                // 基于属性生成代码...
            }
        }
    }
}

关键要点:

  • 使用 compilation.GetSemanticModel() 获取语义信息
  • 使用 GetDeclaredSymbol() 从语法节点获取符号
  • 通过符号访问成员、类型和可访问性

5. 增量生成以提高性能

使用增量生成器获得更好的性能和缓存。

csharp
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Immutable;
using System.Linq;

[Generator]
public class IncrementalGeneratorExample : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // 步骤 1:查找所有类声明
        var classDeclarations = context.SyntaxProvider
            .CreateSyntaxProvider(
                predicate: (node, _) => node is ClassDeclarationSyntax,
                transform: (ctx, _) => (ClassDeclarationSyntax)ctx.Node)
            .Where(c => c is not null);
        
        // 步骤 2:收集所有类
        var compilationAndClasses = context.CompilationProvider
            .Combine(classDeclarations.Collect());
        
        // 步骤 3:生成代码
        context.RegisterSourceOutput(compilationAndClasses,
            (spc, source) => GenerateCode(spc, source.Left, source.Right));
    }
    
    private void GenerateCode(
        SourceProductionContext context,
        Compilation compilation,
        ImmutableArray<ClassDeclarationSyntax> classes)
    {
        foreach (var classDecl in classes)
        {
            var className = classDecl.Identifier.Text;
            var code = $"// 为 {className} 生成";
            context.AddSource($"{className}.g.cs", code);
        }
    }
}

关键要点:

  • 增量生成器缓存中间结果
  • 使用 CreateSyntaxProvider() 进行自定义过滤
  • 使用 Combine() 合并数据源
  • 使用 RegisterSourceOutput() 进行最终代码生成

📋 快速参考表

我想要...使用此 API示例
生成代码context.AddSource()示例 1
查找带特性的类ForAttributeWithMetadataName()示例 2
获取类型信息SemanticModel.GetDeclaredSymbol()示例 4
报告错误/警告context.ReportDiagnostic()示例 3
创建语法节点SyntaxFactory 方法代码生成基础
查找所有类Compilation.GetSymbolsWithName()任务索引
获取类属性classSymbol.GetMembers().OfType<IPropertySymbol>()示例 4
优化性能使用 IIncrementalGenerator示例 5
访问编译context.Compilation编译 API
解析源代码CSharpSyntaxTree.ParseText()代码生成 API

🗺️ 学习路径

初学者(刚开始)

如果你是 Roslyn Source Generators 的新手,请遵循以下路径:

  1. 从学习路径开始

  2. 阅读基础 API 文档

  3. 尝试简单示例

    • 使用任务索引查找常见任务的解决方案
    • 尝试本快速入门指南中的示例

预计时间: 2-3 小时

中级用户(已完成基础示例)

你已经构建了一个简单的生成器,想要学习更多:

  1. 学习高级概念

  2. 探索任务索引

    • 浏览任务索引查找特定问题的解决方案
    • 研究同一问题的不同方法
  3. 练习实际场景

    • 构建 DTO 生成器
    • 创建序列化生成器
    • 实现构建器模式生成器

预计时间: 5-10 小时

高级用户(需要深入理解)

你正在构建复杂的生成器,需要全面的知识:

  1. 学习完整参考

  2. 掌握高级主题

  3. 学习最佳实践

预计时间: 20+ 小时


💡 成功提示

从简单开始

  • 从基于字符串的代码生成开始
  • 从一开始就使用增量生成器以获得更好的性能
  • 使用小而专注的示例进行测试

使用正确的工具

  • 查找带特性的类型: 使用 ForAttributeWithMetadataName()
  • 获取类型信息: 使用 SemanticModel
  • 代码生成: 从字符串开始,需要时转向 SyntaxFactory
  • 性能: 始终使用 IIncrementalGenerator

避免常见陷阱

  • ❌ 不要在新项目中使用 ISourceGenerator(改用 IIncrementalGenerator
  • ❌ 不要在没有适当错误处理的情况下生成代码
  • ❌ 不要忘记将目标类设为 partial
  • ❌ 不要忽略诊断消息

获取帮助


🔗 下一步

现在你已经了解了基础知识,接下来该做什么:

  1. 尝试示例 - 复制并运行上面的代码示例
  2. 探索任务索引 - 在任务索引中查找特定任务的解决方案
  3. 遵循学习路径 - 完成学习路径中的分步教程
  4. 阅读基础指南 - 在基础部分深入了解特定 API

准备好开始了吗? 选择上面的一个示例,在你的项目中尝试一下!

基于 MIT 许可发布