Skip to content

编译 API 基础

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

🎯 学习目标

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

  • [ ] 理解什么是 Compilation 以及为什么需要它
  • [ ] 创建基本的 CSharpCompilation
  • [ ] 添加语法树到编译
  • [ ] 添加元数据引用
  • [ ] 获取语义模型

📖 核心概念

什么是 Compilation?

Compilation 是 Roslyn 中表示整个编译单元的核心类。它包含:

  • 所有源代码文件(作为语法树)
  • 所有程序集引用(如 System.dll)
  • 编译选项(如目标框架、输出类型)

在 Source Generator 中,你通过 context.Compilation 访问当前项目的编译信息。

为什么需要 Compilation?

Compilation 提供了:

  1. 语义分析能力 - 获取类型信息、符号解析
  2. 跨文件查找 - 在整个项目中查找类型和成员
  3. 类型检查 - 验证类型兼容性、继承关系
  4. 程序集引用 - 访问外部类型(如 System.String

🔧 常用方法

方法 1:访问 Compilation

用途: 在 Source Generator 中获取编译对象

何时使用: 几乎所有需要类型信息的场景

示例:

csharp
using Microsoft.CodeAnalysis;

[Generator]
public class MyGenerator : ISourceGenerator
{
    public void Execute(GeneratorExecutionContext context)
    {
        // 获取当前编译
        var compilation = context.Compilation;
        
        // 获取程序集名称
        var assemblyName = compilation.AssemblyName;
        
        // 获取所有语法树
        var syntaxTrees = compilation.SyntaxTrees;
        
        // 获取所有引用
        var references = compilation.References;
    }
    
    public void Initialize(GeneratorInitializationContext context) { }
}

关键要点:

  • Execute 方法中通过 context.Compilation 访问
  • Compilation 对象是不可变的
  • 包含整个项目的所有信息

方法 2:获取语义模型

用途: 从语法树获取语义信息(类型、符号等)

何时使用: 需要分析代码的类型信息时

示例:

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

public class SemanticModelExample
{
    public void AnalyzeCode(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);
        
        if (classSymbol != null)
        {
            var className = classSymbol.Name;
            var namespaceName = classSymbol.ContainingNamespace.ToDisplayString();
        }
    }
}

关键要点:

  • 每个语法树都有对应的语义模型
  • 语义模型提供类型信息和符号解析
  • 使用 GetDeclaredSymbol() 从语法节点获取符号

方法 3:查找类型

用途: 在编译中查找特定类型

何时使用: 需要检查某个类型是否存在,或获取类型信息

示例:

csharp
using Microsoft.CodeAnalysis;

public class TypeLookupExample
{
    public void FindTypes(Compilation compilation)
    {
        // 方法 1: 通过完全限定名查找类型
        var stringType = compilation.GetTypeByMetadataName("System.String");
        
        if (stringType != null)
        {
            // 找到了 System.String 类型
            var typeName = stringType.Name; // "String"
            var fullName = stringType.ToDisplayString(); // "System.String"
        }
        
        // 方法 2: 查找自定义类型
        var myType = compilation.GetTypeByMetadataName("MyNamespace.MyClass");
        
        if (myType != null)
        {
            // 检查类型属性
            var isPublic = myType.DeclaredAccessibility == Accessibility.Public;
            var isSealed = myType.IsSealed;
            var isAbstract = myType.IsAbstract;
        }
        
        // 方法 3: 查找泛型类型
        var listType = compilation.GetTypeByMetadataName("System.Collections.Generic.List`1");
    }
}

关键要点:

  • 使用 GetTypeByMetadataName() 查找类型
  • 需要提供完全限定名(包括命名空间)
  • 泛型类型需要添加 `1 后缀(1 表示类型参数数量)
  • 如果类型不存在,返回 null

方法 4:添加元数据引用

用途: 创建包含外部程序集引用的编译

何时使用: 在单元测试或独立工具中创建编译时

示例:

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

public class ReferenceExample
{
    public CSharpCompilation CreateCompilationWithReferences(string sourceCode)
    {
        // 解析源代码
        var syntaxTree = CSharpSyntaxTree.ParseText(sourceCode);
        
        // 添加常用的程序集引用
        var references = new[]
        {
            // mscorlib (包含 System.Object, System.String 等)
            MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
            
            // System.Runtime
            MetadataReference.CreateFromFile(typeof(Console).Assembly.Location),
            
            // System.Linq
            MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location)
        };
        
        // 创建编译
        var compilation = CSharpCompilation.Create(
            assemblyName: "MyAssembly",
            syntaxTrees: new[] { syntaxTree },
            references: references);
        
        return compilation;
    }
}

关键要点:

  • 使用 MetadataReference.CreateFromFile() 从程序集文件创建引用
  • 使用 typeof(Type).Assembly.Location 获取程序集路径
  • 在 Source Generator 中,通常不需要手动添加引用(已由项目提供)

方法 5:遍历所有语法树

用途: 访问项目中的所有源文件

何时使用: 需要分析整个项目的代码时

示例:

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

public class SyntaxTreeTraversal
{
    public void AnalyzeAllFiles(Compilation compilation)
    {
        // 遍历所有语法树
        foreach (var syntaxTree in compilation.SyntaxTrees)
        {
            // 获取文件路径
            var filePath = syntaxTree.FilePath;
            
            // 获取根节点
            var root = syntaxTree.GetRoot();
            
            // 查找所有类
            var classes = root.DescendantNodes()
                .OfType<ClassDeclarationSyntax>();
            
            foreach (var classDecl in classes)
            {
                var className = classDecl.Identifier.Text;
                // 处理类...
            }
        }
    }
}

关键要点:

  • compilation.SyntaxTrees 包含所有源文件
  • 每个语法树对应一个 .cs 文件
  • 使用 FilePath 属性获取文件路径

💡 实际示例

示例 1:检查类型是否存在

场景: 在生成代码前,检查用户是否引用了必要的 NuGet 包

解决方案:

csharp
using Microsoft.CodeAnalysis;

[Generator]
public class DependencyCheckGenerator : ISourceGenerator
{
    public void Execute(GeneratorExecutionContext context)
    {
        var compilation = context.Compilation;
        
        // 检查是否引用了 Newtonsoft.Json
        var jsonType = compilation.GetTypeByMetadataName("Newtonsoft.Json.JsonConvert");
        
        if (jsonType == null)
        {
            // 报告错误:缺少必要的引用
            var descriptor = new DiagnosticDescriptor(
                id: "GEN001",
                title: "缺少必要的引用",
                messageFormat: "请添加 Newtonsoft.Json NuGet 包引用",
                category: "Generator",
                DiagnosticSeverity.Error,
                isEnabledByDefault: true);
            
            context.ReportDiagnostic(
                Diagnostic.Create(descriptor, Location.None));
            
            return;
        }
        
        // 继续生成代码...
    }
    
    public void Initialize(GeneratorInitializationContext context) { }
}

说明:

  • 使用 GetTypeByMetadataName() 检查类型是否存在
  • 如果类型不存在(返回 null),报告错误
  • 这可以帮助用户及早发现缺少的依赖

示例 2:获取所有公共类

场景: 为项目中的所有公共类生成扩展方法

解决方案:

csharp
using Microsoft.CodeAnalysis;
using System.Collections.Generic;
using System.Linq;

[Generator]
public class ExtensionMethodGenerator : ISourceGenerator
{
    public void Execute(GeneratorExecutionContext context)
    {
        var compilation = context.Compilation;
        
        // 获取所有公共类
        var publicClasses = GetAllPublicClasses(compilation);
        
        foreach (var classSymbol in publicClasses)
        {
            var className = classSymbol.Name;
            var namespaceName = classSymbol.ContainingNamespace.ToDisplayString();
            
            // 为每个类生成扩展方法
            var code = GenerateExtensionMethod(className, namespaceName);
            context.AddSource($"{className}Extensions.g.cs", code);
        }
    }
    
    private IEnumerable<INamedTypeSymbol> GetAllPublicClasses(Compilation compilation)
    {
        // 使用 GetSymbolsWithName 查找所有类型
        return compilation.GetSymbolsWithName(
                name => true, // 接受所有名称
                SymbolFilter.Type)
            .OfType<INamedTypeSymbol>()
            .Where(t => t.TypeKind == TypeKind.Class)
            .Where(t => t.DeclaredAccessibility == Accessibility.Public);
    }
    
    private string GenerateExtensionMethod(string className, string namespaceName)
    {
        return $@"
namespace {namespaceName}
{{
    public static class {className}Extensions
    {{
        public static string ToDebugString(this {className} obj)
        {{
            return obj?.ToString() ?? ""null"";
        }}
    }}
}}";
    }
    
    public void Initialize(GeneratorInitializationContext context) { }
}

说明:

  • 使用 GetSymbolsWithName() 查找所有类型
  • 过滤出公共类
  • 为每个类生成扩展方法

⏭️ 下一步

🔗 相关资源

📚 在学习路径中使用

此 API 在以下步骤中使用:

基于 MIT 许可发布