编译 API 基础
⏱️ 5-10 分钟 | 📚 基础级别
🎯 学习目标
完成本指南后,你将能够:
- [ ] 理解什么是 Compilation 以及为什么需要它
- [ ] 创建基本的 CSharpCompilation
- [ ] 添加语法树到编译
- [ ] 添加元数据引用
- [ ] 获取语义模型
📖 核心概念
什么是 Compilation?
Compilation 是 Roslyn 中表示整个编译单元的核心类。它包含:
- 所有源代码文件(作为语法树)
- 所有程序集引用(如 System.dll)
- 编译选项(如目标框架、输出类型)
在 Source Generator 中,你通过 context.Compilation 访问当前项目的编译信息。
为什么需要 Compilation?
Compilation 提供了:
- 语义分析能力 - 获取类型信息、符号解析
- 跨文件查找 - 在整个项目中查找类型和成员
- 类型检查 - 验证类型兼容性、继承关系
- 程序集引用 - 访问外部类型(如
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 在以下步骤中使用:
- 步骤 1:理解 Source Generators - 基本概念
- 步骤 3:基于特性的生成 - 使用语义模型
- 步骤 4:增量生成器 - 高级用法