Skip to content

Roslyn 编译器平台详解

深入了解 .NET 编译器平台

概述

Roslyn 是 .NET 编译器平台的代号,它是 Microsoft 开发的开源编译器和代码分析 API 集合。Roslyn 为 C# 和 Visual Basic 提供了丰富的编译器 API,使开发者能够构建代码分析工具、重构工具、IDE 功能和源生成器。

什么是 Roslyn?

Roslyn 不仅仅是一个编译器,它是一个完整的编译器平台,提供:

  1. 编译器即服务 (Compiler as a Service)

    • 将编译器功能作为 API 暴露
    • 允许程序化访问编译过程的各个阶段
    • 支持增量编译和实时分析
  2. 丰富的 API

    • 语法 API:访问和操作语法树
    • 语义 API:查询类型信息和符号
    • 工作区 API:管理项目和解决方案
    • 脚本 API:动态执行 C# 代码
  3. 开源和跨平台

    • 完全开源(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; }"

语法树的特点

  1. 不可变 (Immutable)

    • 一旦创建,不能修改
    • 修改需要创建新的树
    • 支持高效的并发访问
  2. 完整性 (Lossless)

    • 包含所有源代码信息
    • 包括空白、注释、格式
    • 可以完美重建原始代码
  3. 红绿树 (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)

语义模型提供代码的语义信息,包括类型、符号、绑定等。

语义模型的作用

  1. 类型信息

    • 获取表达式的类型
    • 查询类型的成员
    • 检查类型兼容性
  2. 符号信息

    • 获取声明的符号
    • 查找符号的引用
    • 分析符号的可访问性
  3. 绑定信息

    • 解析名称引用
    • 查找重载方法
    • 分析作用域

符号 (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 Succinctly" by Alessandro Del Sole
  • ".NET Development Using the Compiler API" by Jason Bock

下一步: 阅读 编译时 vs 运行时 了解何时使用源生成器。

基于 MIT 许可发布