Roslyn 编译器平台详解
深入了解 .NET 编译器平台
概述
Roslyn 是 .NET 编译器平台的代号,它是 Microsoft 开发的开源编译器和代码分析 API 集合。Roslyn 为 C# 和 Visual Basic 提供了丰富的编译器 API,使开发者能够构建代码分析工具、重构工具、IDE 功能和源生成器。
什么是 Roslyn?
Roslyn 不仅仅是一个编译器,它是一个完整的编译器平台,提供:
编译器即服务 (Compiler as a Service)
- 将编译器功能作为 API 暴露
- 允许程序化访问编译过程的各个阶段
- 支持增量编译和实时分析
丰富的 API
- 语法 API:访问和操作语法树
- 语义 API:查询类型信息和符号
- 工作区 API:管理项目和解决方案
- 脚本 API:动态执行 C# 代码
开源和跨平台
- 完全开源(MIT 许可证)
- 支持 Windows、Linux、macOS
- 活跃的社区贡献
Roslyn 的架构层次
┌─────────────────────────────────────────┐
│ 应用层 (Applications) │
│ IDE、分析器、重构工具、源生成器 │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 工作区 API (Workspace API) │
│ 项目管理、解决方案、文档操作 │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 编译 API (Compilation API) │
│ 语义模型、符号、类型信息 │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 语法 API (Syntax API) │
│ 语法树、语法节点、语法标记 │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 解析器 (Parser) │
│ 词法分析、语法分析 │
└─────────────────────────────────────────┘语法树和语义模型
语法树 (Syntax Tree)
语法树是代码的结构化表示,它将源代码解析为树形结构。
语法树的组成
SyntaxTree
├── CompilationUnit (根节点)
│ ├── Usings (using 指令)
│ ├── Members (成员)
│ │ ├── NamespaceDeclaration (命名空间)
│ │ │ └── Members
│ │ │ ├── ClassDeclaration (类)
│ │ │ │ └── Members
│ │ │ │ ├── FieldDeclaration (字段)
│ │ │ │ ├── PropertyDeclaration (属性)
│ │ │ │ └── MethodDeclaration (方法)
│ │ │ │ └── Body (方法体)
│ │ │ │ └── Statements (语句)
└── EndOfFileToken语法树示例
对于以下代码:
csharp
using System;
namespace MyApp
{
public class Person
{
public string Name { get; set; }
}
}语法树结构:
CompilationUnitSyntax
├── UsingDirectiveSyntax: "using System;"
└── NamespaceDeclarationSyntax: "namespace MyApp"
└── ClassDeclarationSyntax: "public class Person"
└── PropertyDeclarationSyntax: "public string Name { get; set; }"语法树的特点
不可变 (Immutable)
- 一旦创建,不能修改
- 修改需要创建新的树
- 支持高效的并发访问
完整性 (Lossless)
- 包含所有源代码信息
- 包括空白、注释、格式
- 可以完美重建原始代码
红绿树 (Red-Green Tree)
- 绿色节点:不可变、可共享
- 红色节点:包含父节点引用
- 优化内存使用和性能
访问语法树
csharp
// 解析代码
var tree = CSharpSyntaxTree.ParseText(@"
public class Person
{
public string Name { get; set; }
}
");
// 获取根节点
var root = tree.GetRoot();
// 查找所有类声明
var classes = root.DescendantNodes()
.OfType<ClassDeclarationSyntax>();
foreach (var classDecl in classes)
{
Console.WriteLine($"类名: {classDecl.Identifier.Text}");
// 查找属性
var properties = classDecl.Members
.OfType<PropertyDeclarationSyntax>();
foreach (var prop in properties)
{
Console.WriteLine($" 属性: {prop.Identifier.Text}");
}
}语义模型 (Semantic Model)
语义模型提供代码的语义信息,包括类型、符号、绑定等。
语义模型的作用
类型信息
- 获取表达式的类型
- 查询类型的成员
- 检查类型兼容性
符号信息
- 获取声明的符号
- 查找符号的引用
- 分析符号的可访问性
绑定信息
- 解析名称引用
- 查找重载方法
- 分析作用域
符号 (Symbols)
符号表示代码中的声明实体:
ISymbol (基类)
├── INamespaceSymbol (命名空间)
├── INamedTypeSymbol (类、结构、接口、枚举)
├── IMethodSymbol (方法)
├── IPropertySymbol (属性)
├── IFieldSymbol (字段)
├── IEventSymbol (事件)
├── IParameterSymbol (参数)
└── ILocalSymbol (局部变量)使用语义模型
csharp
// 创建编译
var compilation = CSharpCompilation.Create("MyCompilation")
.AddReferences(MetadataReference.CreateFromFile(
typeof(object).Assembly.Location))
.AddSyntaxTrees(tree);
// 获取语义模型
var semanticModel = compilation.GetSemanticModel(tree);
// 查找类声明
var classDecl = root.DescendantNodes()
.OfType<ClassDeclarationSyntax>()
.First();
// 获取类的符号
var classSymbol = semanticModel.GetDeclaredSymbol(classDecl)
as INamedTypeSymbol;
Console.WriteLine($"类的完全限定名: {classSymbol.ToDisplayString()}");
// 获取所有公共属性
var publicProperties = classSymbol.GetMembers()
.OfType<IPropertySymbol>()
.Where(p => p.DeclaredAccessibility == Accessibility.Public);
foreach (var prop in publicProperties)
{
Console.WriteLine($"属性: {prop.Name}, 类型: {prop.Type.Name}");
}语法树 vs 语义模型
| 特性 | 语法树 | 语义模型 |
|---|---|---|
| 信息类型 | 结构信息 | 语义信息 |
| 依赖 | 独立(单个文件) | 依赖编译(多个文件) |
| 性能 | 快(只解析) | 慢(需要绑定) |
| 用途 | 代码格式化、语法检查 | 类型分析、重构 |
| 示例 | 查找所有类 | 查找类的基类 |
何时使用语法树:
- 只需要代码结构信息
- 不需要类型信息
- 性能关键的场景
- 代码格式化、语法高亮
何时使用语义模型:
- 需要类型信息
- 需要符号解析
- 需要跨文件分析
- 重构、代码分析
常用 API 介绍
1. 语法 API
SyntaxNode - 语法节点
csharp
// 查找特定类型的节点
var methods = root.DescendantNodes()
.OfType<MethodDeclarationSyntax>();
// 遍历子节点
foreach (var child in node.ChildNodes())
{
// 处理子节点
}
// 查找父节点
var parent = node.Parent;
// 获取节点的文本
var text = node.ToString();
// 获取节点的位置
var span = node.Span;
var location = node.GetLocation();SyntaxToken - 语法标记
csharp
// 获取标识符
var identifier = classDecl.Identifier;
Console.WriteLine(identifier.Text); // "Person"
// 检查修饰符
var isPublic = classDecl.Modifiers
.Any(m => m.IsKind(SyntaxKind.PublicKeyword));
// 获取所有标记
var tokens = node.DescendantTokens();SyntaxTrivia - 语法琐碎内容
csharp
// 获取前导琐碎内容(注释、空白)
var leadingTrivia = token.LeadingTrivia;
// 获取注释
var comments = node.DescendantTrivia()
.Where(t => t.IsKind(SyntaxKind.SingleLineCommentTrivia) ||
t.IsKind(SyntaxKind.MultiLineCommentTrivia));2. 语义 API
Compilation - 编译
csharp
// 创建编译
var compilation = CSharpCompilation.Create("MyCompilation")
.AddReferences(
MetadataReference.CreateFromFile(typeof(object).Assembly.Location))
.AddSyntaxTrees(tree);
// 获取诊断(错误、警告)
var diagnostics = compilation.GetDiagnostics();
// 获取程序集符号
var assemblySymbol = compilation.Assembly;
// 获取全局命名空间
var globalNamespace = compilation.GlobalNamespace;SemanticModel - 语义模型
csharp
var semanticModel = compilation.GetSemanticModel(tree);
// 获取声明的符号
var symbol = semanticModel.GetDeclaredSymbol(node);
// 获取表达式的类型
var typeInfo = semanticModel.GetTypeInfo(expression);
var type = typeInfo.Type;
// 获取符号信息
var symbolInfo = semanticModel.GetSymbolInfo(expression);
var referencedSymbol = symbolInfo.Symbol;
// 查找作用域内的符号
var symbols = semanticModel.LookupSymbols(position);ISymbol - 符号
csharp
// 符号的基本信息
var name = symbol.Name;
var kind = symbol.Kind; // Method, Property, Field, etc.
var accessibility = symbol.DeclaredAccessibility;
// 获取包含类型
var containingType = symbol.ContainingType;
// 获取包含命名空间
var containingNamespace = symbol.ContainingNamespace;
// 获取特性
var attributes = symbol.GetAttributes();
// 显示字符串
var displayString = symbol.ToDisplayString();INamedTypeSymbol - 类型符号
csharp
var typeSymbol = symbol as INamedTypeSymbol;
// 获取基类
var baseType = typeSymbol.BaseType;
// 获取接口
var interfaces = typeSymbol.Interfaces;
// 获取成员
var members = typeSymbol.GetMembers();
var methods = typeSymbol.GetMembers()
.OfType<IMethodSymbol>();
// 检查类型关系
var isClass = typeSymbol.TypeKind == TypeKind.Class;
var isInterface = typeSymbol.TypeKind == TypeKind.Interface;
// 获取泛型参数
var typeParameters = typeSymbol.TypeParameters;3. 源生成器专用 API
GeneratorExecutionContext
csharp
public void Execute(GeneratorExecutionContext context)
{
// 获取编译
var compilation = context.Compilation;
// 获取语法树
var syntaxTrees = compilation.SyntaxTrees;
// 添加源代码
context.AddSource("Generated.g.cs", sourceCode);
// 报告诊断
context.ReportDiagnostic(diagnostic);
// 获取分析器配置选项
var options = context.AnalyzerConfigOptions;
}IncrementalGeneratorInitializationContext
csharp
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// 创建语法提供者
var provider = context.SyntaxProvider.CreateSyntaxProvider(
predicate: (node, _) => node is ClassDeclarationSyntax,
transform: (ctx, _) => ctx.Node);
// 使用 ForAttributeWithMetadataName
var attributeProvider = context.SyntaxProvider
.ForAttributeWithMetadataName(
"MyNamespace.MyAttribute",
predicate: (node, _) => node is ClassDeclarationSyntax,
transform: (ctx, _) => ctx);
// 注册输出
context.RegisterSourceOutput(provider, Execute);
// 注册初始化输出
context.RegisterPostInitializationOutput(ctx =>
{
ctx.AddSource("Attribute.g.cs", attributeCode);
});
}4. 实用工具 API
SyntaxFactory - 创建语法节点
csharp
// 创建类声明
var classDecl = SyntaxFactory.ClassDeclaration("MyClass")
.AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword))
.AddMembers(/* 成员 */);
// 创建属性
var property = SyntaxFactory.PropertyDeclaration(
SyntaxFactory.ParseTypeName("string"),
"Name")
.AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword))
.AddAccessorListAccessors(
SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)
.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)),
SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration)
.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)));SymbolDisplay - 格式化符号
csharp
// 完全限定名
var fullName = symbol.ToDisplayString(
SymbolDisplayFormat.FullyQualifiedFormat);
// 最小限定名
var minimalName = symbol.ToDisplayString(
SymbolDisplayFormat.MinimallyQualifiedFormat);
// 自定义格式
var format = new SymbolDisplayFormat(
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters);
var customName = symbol.ToDisplayString(format);最佳实践
1. 性能优化
csharp
// ✅ 好:使用 LINQ 延迟执行
var classes = root.DescendantNodes()
.OfType<ClassDeclarationSyntax>()
.Where(c => c.Modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword)));
// ❌ 差:多次遍历
var allNodes = root.DescendantNodes().ToList();
var classes = allNodes.OfType<ClassDeclarationSyntax>().ToList();
var publicClasses = classes.Where(c => /* ... */).ToList();2. 语法 vs 语义
csharp
// ✅ 好:先用语法过滤,再用语义
var candidates = root.DescendantNodes()
.OfType<ClassDeclarationSyntax>()
.Where(c => c.AttributeLists.Count > 0); // 语法过滤
foreach (var candidate in candidates)
{
var symbol = semanticModel.GetDeclaredSymbol(candidate);
if (symbol?.GetAttributes().Any(a => a.AttributeClass?.Name == "MyAttribute") == true)
{
// 语义检查
}
}
// ❌ 差:直接用语义(慢)
var allClasses = root.DescendantNodes()
.OfType<ClassDeclarationSyntax>();
foreach (var classDecl in allClasses)
{
var symbol = semanticModel.GetDeclaredSymbol(classDecl);
// 每个类都查询语义模型
}3. 空值检查
csharp
// ✅ 好:始终检查空值
var symbol = semanticModel.GetDeclaredSymbol(node);
if (symbol is INamedTypeSymbol typeSymbol)
{
// 使用 typeSymbol
}
// ❌ 差:假设不为空
var typeSymbol = (INamedTypeSymbol)semanticModel.GetDeclaredSymbol(node);4. 使用模式匹配
csharp
// ✅ 好:使用模式匹配
if (node is ClassDeclarationSyntax { Identifier.Text: "Person" } classDecl)
{
// 使用 classDecl
}
// ❌ 差:多次类型检查和转换
if (node is ClassDeclarationSyntax)
{
var classDecl = (ClassDeclarationSyntax)node;
if (classDecl.Identifier.Text == "Person")
{
// ...
}
}学习资源
官方文档
工具
- Roslyn Quoter - 将 C# 代码转换为 Roslyn API 调用
- SharpLab - 在线查看语法树和 IL
- LINQPad - 交互式探索 Roslyn API
书籍
- "Roslyn Succinctly" by Alessandro Del Sole
- ".NET Development Using the Compiler API" by Jason Bock
下一步: 阅读 编译时 vs 运行时 了解何时使用源生成器。