Skip to content

Roslyn 原理

什么是 Roslyn?

Roslyn 是 .NET 编译器平台的代号,它是 C# 和 Visual Basic 的开源编译器。

编译流程

核心组件

1. 语法分析器 (Parser)

  • 输入: 源代码文本
  • 输出: 语法树 (SyntaxTree)
  • 功能: 将文本转换为结构化的语法树

2. 语义分析器 (Semantic Analyzer)

  • 输入: 语法树 + 引用
  • 输出: 语义模型 (SemanticModel)
  • 功能: 类型检查、符号解析、绑定

3. 源生成器 (Source Generator)

  • 位置: 在语义分析之后,代码生成之前
  • 功能: 分析代码并生成新的源文件

4. 代码生成器 (Code Generator)

  • 输入: 语义模型
  • 输出: IL 代码
  • 功能: 生成中间语言代码

Roslyn API 架构

三层架构

  1. 编译器 API (源生成器使用这一层)

    • 最底层的 API
    • 提供完整的编译功能
    • 包含语法树和语义模型
  2. 工作区 API

    • 中间层 API
    • 管理项目、解决方案和文档
    • 用于 IDE 功能
  3. 脚本 API

    • 最高层 API
    • 提供动态代码执行
    • 用于 REPL 场景

核心概念

语法树 (Syntax Tree)

  • 源代码的结构化表示
  • 包含所有信息(包括空格、注释)
  • 不可变

语义模型 (Semantic Model)

  • 提供类型信息
  • 符号解析
  • 类型检查

符号 (Symbol)

  • 表示代码中的实体
  • 类型、方法、属性、字段等
  • 通过语义模型获取

下一步

Roslyn 的历史

编译器的演进

为什么需要 Roslyn?

传统编译器的问题

  1. 黑盒设计

    • 输入源代码,输出程序集
    • 无法访问编译过程中的信息
    • 难以构建代码分析工具
  2. C++ 实现

    • 难以扩展
    • 无法在 .NET 中使用
    • 维护成本高
  3. 缺乏 API

    • IDE 功能需要重新实现
    • 代码分析工具难以开发
    • 无法复用编译器逻辑

Roslyn 的解决方案

  • 编译器即服务:提供完整的 API
  • C# 实现:易于扩展和维护
  • 开源:社区可以贡献
  • 统一平台:IDE 和工具使用相同的编译器

编译管道详解

完整的编译流程

各阶段详解

1. 词法分析(Lexical Analysis)

功能:将源代码文本分解为 Token 序列

csharp
// 源代码
public class Person { }

// Token 序列
[
    { Kind: PublicKeyword, Text: "public" },
    { Kind: ClassKeyword, Text: "class" },
    { Kind: IdentifierToken, Text: "Person" },
    { Kind: OpenBraceToken, Text: "{" },
    { Kind: CloseBraceToken, Text: "}" }
]

代码示例

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

// 词法分析示例
var code = "public class Person { }";
var syntaxTree = CSharpSyntaxTree.ParseText(code);

// 获取所有 Token
foreach (var token in syntaxTree.GetRoot().DescendantTokens())
{
    Console.WriteLine($"Kind: {token.Kind()}, Text: '{token.Text}'");
}

// 输出:
// Kind: PublicKeyword, Text: 'public'
// Kind: ClassKeyword, Text: 'class'
// Kind: IdentifierToken, Text: 'Person'
// Kind: OpenBraceToken, Text: '{'
// Kind: CloseBraceToken, Text: '}'

2. 语法分析(Syntax Analysis)

功能:将 Token 序列组织成语法树

csharp
// Token 序列 → 语法树
CompilationUnit
└── ClassDeclaration (Person)
    ├── Modifiers: public
    ├── Keyword: class
    ├── Identifier: Person
    └── Members: (empty)

代码示例

csharp
using Microsoft.CodeAnalysis.CSharp.Syntax;

var code = @"
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}";

var tree = CSharpSyntaxTree.ParseText(code);
var root = tree.GetRoot();

// 查找类声明
var classDecl = root.DescendantNodes()
    .OfType<ClassDeclarationSyntax>()
    .First();

Console.WriteLine($"Class Name: {classDecl.Identifier.Text}");
Console.WriteLine($"Modifiers: {string.Join(" ", classDecl.Modifiers)}");
Console.WriteLine($"Properties: {classDecl.Members.Count}");

// 输出:
// Class Name: Person
// Modifiers: public
// Properties: 2

3. 语义分析(Semantic Analysis)

功能:类型检查、符号解析、绑定

csharp
// 语法树 + 引用 → 语义模型
Person person = new Person();
       ↓           ↓
    类型检查    构造函数解析

代码示例

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

var code = @"
class Person
{
    public string Name { get; set; }
}

class Program
{
    void Main()
    {
        var person = new Person();
        person.Name = ""张三"";
    }
}";

// 创建编译
var tree = CSharpSyntaxTree.ParseText(code);
var compilation = CSharpCompilation.Create("MyCompilation")
    .AddReferences(MetadataReference.CreateFromFile(
        typeof(object).Assembly.Location))
    .AddSyntaxTrees(tree);

// 获取语义模型
var semanticModel = compilation.GetSemanticModel(tree);

// 查找变量声明
var root = tree.GetRoot();
var variableDecl = root.DescendantNodes()
    .OfType<VariableDeclaratorSyntax>()
    .First(v => v.Identifier.Text == "person");

// 获取类型信息
var typeInfo = semanticModel.GetTypeInfo(variableDecl.Parent.Parent);
Console.WriteLine($"Variable Type: {typeInfo.Type.Name}");
// 输出: Variable Type: Person

4. 源生成(Source Generation)

功能:分析代码并生成新的源文件

csharp
// 语义模型 → 源生成器 → 新源文件
[GenerateToString]
public partial class Person { }

源生成器分析

public partial class Person
{
    public override string ToString() => /* ... */;
}

代码示例

csharp
[Generator]
public class ToStringGenerator : ISourceGenerator
{
    public void Execute(GeneratorExecutionContext context)
    {
        // 1. 获取编译信息
        var compilation = context.Compilation;
        
        // 2. 查找标记的类
        foreach (var syntaxTree in compilation.SyntaxTrees)
        {
            var semanticModel = compilation.GetSemanticModel(syntaxTree);
            var root = syntaxTree.GetRoot();
            
            foreach (var classDecl in root.DescendantNodes()
                .OfType<ClassDeclarationSyntax>())
            {
                var symbol = semanticModel.GetDeclaredSymbol(classDecl);
                
                // 3. 检查是否有特性
                if (HasGenerateToStringAttribute(symbol))
                {
                    // 4. 生成代码
                    var code = GenerateToStringMethod(symbol);
                    context.AddSource($"{symbol.Name}.g.cs", code);
                }
            }
        }
    }
}

5. IL 生成(IL Emission)

功能:将语义模型转换为 IL 代码

csharp
// 语义模型 → IL 代码
public string Name { get; set; }

.property instance string Name()
{
    .get instance string Person::get_Name()
    .set instance void Person::set_Name(string)
}

语法树、语义模型和符号的关系

三者的关系图

详细说明

  1. 语法树(SyntaxTree)

    • 源代码的结构化表示
    • 包含所有文本信息(包括空格、注释)
    • 不包含类型信息
    • 不可变
  2. 语义模型(SemanticModel)

    • 连接语法树和符号的桥梁
    • 提供类型信息
    • 提供符号解析
    • 每个语法树对应一个语义模型
  3. 符号(Symbol)

    • 表示代码中的实体
    • 类型、方法、属性、字段等
    • 包含完整的元数据
    • 可以跨文件引用

代码示例:三者协同工作

csharp
var code = @"
class Person
{
    public string Name { get; set; }
    
    public void SayHello()
    {
        Console.WriteLine($""Hello, {Name}"");
    }
}";

// 1. 创建语法树
var tree = CSharpSyntaxTree.ParseText(code);
var root = tree.GetRoot();

// 2. 创建编译和语义模型
var compilation = CSharpCompilation.Create("Test")
    .AddReferences(MetadataReference.CreateFromFile(
        typeof(object).Assembly.Location))
    .AddSyntaxTrees(tree);

var semanticModel = compilation.GetSemanticModel(tree);

// 3. 查找方法声明(语法树)
var methodDecl = root.DescendantNodes()
    .OfType<MethodDeclarationSyntax>()
    .First(m => m.Identifier.Text == "SayHello");

Console.WriteLine($"Method Name (Syntax): {methodDecl.Identifier.Text}");

// 4. 获取方法符号(语义模型 + 符号)
var methodSymbol = semanticModel.GetDeclaredSymbol(methodDecl) as IMethodSymbol;

Console.WriteLine($"Method Name (Symbol): {methodSymbol.Name}");
Console.WriteLine($"Return Type: {methodSymbol.ReturnType.Name}");
Console.WriteLine($"Containing Type: {methodSymbol.ContainingType.Name}");

// 5. 查找方法体中的标识符
var identifier = methodDecl.DescendantNodes()
    .OfType<IdentifierNameSyntax>()
    .First(i => i.Identifier.Text == "Name");

// 6. 获取标识符的符号信息
var symbolInfo = semanticModel.GetSymbolInfo(identifier);
var propertySymbol = symbolInfo.Symbol as IPropertySymbol;

Console.WriteLine($"Property Name: {propertySymbol.Name}");
Console.WriteLine($"Property Type: {propertySymbol.Type.Name}");

// 输出:
// Method Name (Syntax): SayHello
// Method Name (Symbol): SayHello
// Return Type: Void
// Containing Type: Person
// Property Name: Name
// Property Type: String

使用场景对比

需求使用工具示例
查找类名语法树classDecl.Identifier.Text
获取类型信息语义模型semanticModel.GetTypeInfo(node)
检查继承关系符号symbol.BaseType
查找所有引用符号SymbolFinder.FindReferencesAsync
格式化代码语法树node.NormalizeWhitespace()
类型转换检查语义模型semanticModel.ClassifyConversion

实际应用示例

示例 1: 查找所有公共类

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

public class PublicClassFinder
{
    public static List<string> FindPublicClasses(string code)
    {
        var tree = CSharpSyntaxTree.ParseText(code);
        var root = tree.GetRoot();
        
        var publicClasses = root.DescendantNodes()
            .OfType<ClassDeclarationSyntax>()
            .Where(c => c.Modifiers.Any(SyntaxKind.PublicKeyword))
            .Select(c => c.Identifier.Text)
            .ToList();
        
        return publicClasses;
    }
}

// 使用
var code = @"
public class Person { }
internal class Employee { }
public class Customer { }
";

var classes = PublicClassFinder.FindPublicClasses(code);
Console.WriteLine(string.Join(", ", classes));
// 输出: Person, Customer

示例 2: 查找实现特定接口的类

csharp
public class InterfaceImplementationFinder
{
    public static List<string> FindImplementations(
        Compilation compilation, 
        string interfaceName)
    {
        var results = new List<string>();
        
        foreach (var syntaxTree in compilation.SyntaxTrees)
        {
            var semanticModel = compilation.GetSemanticModel(syntaxTree);
            var root = syntaxTree.GetRoot();
            
            foreach (var classDecl in root.DescendantNodes()
                .OfType<ClassDeclarationSyntax>())
            {
                var classSymbol = semanticModel.GetDeclaredSymbol(classDecl);
                
                // 检查是否实现了指定接口
                if (classSymbol.AllInterfaces.Any(i => i.Name == interfaceName))
                {
                    results.Add(classSymbol.Name);
                }
            }
        }
        
        return results;
    }
}

// 使用
var code = @"
interface IRepository { }

class UserRepository : IRepository { }
class ProductRepository : IRepository { }
class Logger { }
";

var tree = CSharpSyntaxTree.ParseText(code);
var compilation = CSharpCompilation.Create("Test")
    .AddSyntaxTrees(tree);

var implementations = InterfaceImplementationFinder
    .FindImplementations(compilation, "IRepository");

Console.WriteLine(string.Join(", ", implementations));
// 输出: UserRepository, ProductRepository

示例 3: 分析方法复杂度

csharp
public class ComplexityAnalyzer
{
    public static int CalculateCyclomaticComplexity(MethodDeclarationSyntax method)
    {
        int complexity = 1;  // 基础复杂度
        
        // 统计决策点
        var decisionPoints = method.DescendantNodes()
            .Where(node =>
                node is IfStatementSyntax ||
                node is WhileStatementSyntax ||
                node is ForStatementSyntax ||
                node is ForEachStatementSyntax ||
                node is CaseSwitchLabelSyntax ||
                node is ConditionalExpressionSyntax ||
                node is BinaryExpressionSyntax binary && 
                    (binary.IsKind(SyntaxKind.LogicalAndExpression) ||
                     binary.IsKind(SyntaxKind.LogicalOrExpression)))
            .Count();
        
        complexity += decisionPoints;
        return complexity;
    }
}

// 使用
var code = @"
public int CalculateDiscount(int age, bool isMember, decimal amount)
{
    if (age < 18)
        return 0;
    
    if (isMember && amount > 100)
        return 20;
    else if (amount > 50)
        return 10;
    
    return 5;
}";

var tree = CSharpSyntaxTree.ParseText(code);
var method = tree.GetRoot()
    .DescendantNodes()
    .OfType<MethodDeclarationSyntax>()
    .First();

var complexity = ComplexityAnalyzer.CalculateCyclomaticComplexity(method);
Console.WriteLine($"Cyclomatic Complexity: {complexity}");
// 输出: Cyclomatic Complexity: 5

性能考虑

Roslyn API 的性能特点

性能最佳实践

1. 重用编译对象

csharp
// ✅ 推荐:重用编译对象
var compilation = CSharpCompilation.Create("MyCompilation")
    .AddReferences(references)
    .AddSyntaxTrees(trees);

// 多次使用同一个编译对象
var semanticModel1 = compilation.GetSemanticModel(tree1);
var semanticModel2 = compilation.GetSemanticModel(tree2);

// ❌ 避免:每次都创建新的编译对象
foreach (var tree in trees)
{
    var compilation = CSharpCompilation.Create("MyCompilation")
        .AddReferences(references)
        .AddSyntaxTrees(tree);  // 重复创建,性能差
}

2. 使用 SyntaxWalker 而不是递归

csharp
// ✅ 推荐:使用 SyntaxWalker
public class ClassCollector : CSharpSyntaxWalker
{
    public List<ClassDeclarationSyntax> Classes { get; } = new();
    
    public override void VisitClassDeclaration(ClassDeclarationSyntax node)
    {
        Classes.Add(node);
        base.VisitClassDeclaration(node);
    }
}

var walker = new ClassCollector();
walker.Visit(root);
var classes = walker.Classes;

// ❌ 避免:手动递归遍历
void CollectClasses(SyntaxNode node, List<ClassDeclarationSyntax> classes)
{
    if (node is ClassDeclarationSyntax classDecl)
        classes.Add(classDecl);
    
    foreach (var child in node.ChildNodes())
        CollectClasses(child, classes);  // 递归,性能较差
}

3. 避免不必要的语义分析

csharp
// ✅ 推荐:先用语法过滤,再用语义分析
var candidateClasses = root.DescendantNodes()
    .OfType<ClassDeclarationSyntax>()
    .Where(c => c.AttributeLists.Count > 0);  // 语法过滤

foreach (var classDecl in candidateClasses)
{
    var symbol = semanticModel.GetDeclaredSymbol(classDecl);  // 只对候选类进行语义分析
    if (HasSpecificAttribute(symbol))
    {
        // 处理...
    }
}

// ❌ 避免:对所有类进行语义分析
foreach (var classDecl in root.DescendantNodes().OfType<ClassDeclarationSyntax>())
{
    var symbol = semanticModel.GetDeclaredSymbol(classDecl);  // 对所有类都进行语义分析
    if (HasSpecificAttribute(symbol))
    {
        // 处理...
    }
}

4. 使用 LINQ 的延迟执行

csharp
// ✅ 推荐:使用延迟执行
var publicMethods = root.DescendantNodes()
    .OfType<MethodDeclarationSyntax>()
    .Where(m => m.Modifiers.Any(SyntaxKind.PublicKeyword))
    .Take(10);  // 只取前 10 个

// ❌ 避免:立即执行
var allMethods = root.DescendantNodes()
    .OfType<MethodDeclarationSyntax>()
    .ToList();  // 立即执行,处理所有方法

var publicMethods = allMethods
    .Where(m => m.Modifiers.Any(SyntaxKind.PublicKeyword))
    .Take(10);

性能测试示例

csharp
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

[MemoryDiagnoser]
public class RoslynPerformanceBenchmark
{
    private string _code;
    private SyntaxTree _tree;
    private Compilation _compilation;
    
    [GlobalSetup]
    public void Setup()
    {
        _code = GenerateLargeCodeFile();  // 生成大型代码文件
        _tree = CSharpSyntaxTree.ParseText(_code);
        _compilation = CSharpCompilation.Create("Test")
            .AddSyntaxTrees(_tree);
    }
    
    [Benchmark]
    public int CountClasses_WithSyntaxWalker()
    {
        var walker = new ClassCountWalker();
        walker.Visit(_tree.GetRoot());
        return walker.Count;
    }
    
    [Benchmark]
    public int CountClasses_WithLinq()
    {
        return _tree.GetRoot()
            .DescendantNodes()
            .OfType<ClassDeclarationSyntax>()
            .Count();
    }
}

// 结果示例:
// | Method                        | Mean      | Allocated |
// |------------------------------ |----------:|----------:|
// | CountClasses_WithSyntaxWalker |  45.2 ms  |    1.2 KB |
// | CountClasses_WithLinq         |  52.8 ms  |   15.3 KB |

最佳实践

1. 使用正确的 API 层次

csharp
// ✅ 源生成器:使用编译器 API
[Generator]
public class MyGenerator : ISourceGenerator
{
    public void Execute(GeneratorExecutionContext context)
    {
        var compilation = context.Compilation;  // 编译器 API
        // ...
    }
}

// ✅ IDE 扩展:使用工作区 API
public class MyCodeRefactoring : CodeRefactoringProvider
{
    public override async Task ComputeRefactoringsAsync(
        CodeRefactoringContext context)
    {
        var document = context.Document;  // 工作区 API
        // ...
    }
}

// ✅ 脚本执行:使用脚本 API
var script = CSharpScript.Create("Console.WriteLine(\"Hello\")");
await script.RunAsync();  // 脚本 API

2. 正确处理语法树的不可变性

csharp
// ✅ 推荐:使用 With 方法创建修改后的节点
var originalClass = SyntaxFactory.ClassDeclaration("Person");
var modifiedClass = originalClass
    .WithModifiers(SyntaxFactory.TokenList(
        SyntaxFactory.Token(SyntaxKind.PublicKeyword)))
    .WithMembers(SyntaxFactory.List(members));

// ❌ 避免:尝试修改现有节点(不可能,因为不可变)
// originalClass.Modifiers = ...;  // 编译错误

3. 使用 SyntaxFactory 创建语法节点

csharp
// ✅ 推荐:使用 SyntaxFactory
var propertyDecl = SyntaxFactory.PropertyDeclaration(
    SyntaxFactory.PredefinedType(
        SyntaxFactory.Token(SyntaxKind.StringKeyword)),
    "Name")
    .WithModifiers(SyntaxFactory.TokenList(
        SyntaxFactory.Token(SyntaxKind.PublicKeyword)))
    .WithAccessorList(SyntaxFactory.AccessorList(
        SyntaxFactory.List(new[]
        {
            SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)
                .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)),
            SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration)
                .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken))
        })));

// 生成: public string Name { get; set; }

// ❌ 避免:手动拼接字符串
var code = "public string Name { get; set; }";
var tree = CSharpSyntaxTree.ParseText(code);  // 不够灵活

4. 使用 Roslyn Quoter 工具

Roslyn Quoter 是一个在线工具,可以将 C# 代码转换为 SyntaxFactory 调用:

csharp
// 输入代码:
public string Name { get; set; }

// Roslyn Quoter 输出:
SyntaxFactory.PropertyDeclaration(
    SyntaxFactory.PredefinedType(
        SyntaxFactory.Token(SyntaxKind.StringKeyword)),
    SyntaxFactory.Identifier("Name"))
.WithModifiers(
    SyntaxFactory.TokenList(
        SyntaxFactory.Token(SyntaxKind.PublicKeyword)))
.WithAccessorList(
    SyntaxFactory.AccessorList(
        SyntaxFactory.List<AccessorDeclarationSyntax>(
            new AccessorDeclarationSyntax[]{
                SyntaxFactory.AccessorDeclaration(
                    SyntaxKind.GetAccessorDeclaration)
                .WithSemicolonToken(
                    SyntaxFactory.Token(SyntaxKind.SemicolonToken)),
                SyntaxFactory.AccessorDeclaration(
                    SyntaxKind.SetAccessorDeclaration)
                .WithSemicolonToken(
                    SyntaxFactory.Token(SyntaxKind.SemicolonToken))})));

工具地址:https://roslynquoter.azurewebsites.net/

5. 处理 Trivia(空格、注释等)

csharp
// ✅ 推荐:保留或添加适当的 Trivia
var classDecl = SyntaxFactory.ClassDeclaration("Person")
    .WithLeadingTrivia(
        SyntaxFactory.Comment("// This is a person class"),
        SyntaxFactory.CarriageReturnLineFeed)
    .WithModifiers(SyntaxFactory.TokenList(
        SyntaxFactory.Token(
            SyntaxFactory.TriviaList(),  // Leading trivia
            SyntaxKind.PublicKeyword,
            SyntaxFactory.TriviaList(SyntaxFactory.Space))));  // Trailing trivia

// 生成:
// // This is a person class
// public class Person

// ❌ 避免:忽略 Trivia,导致格式混乱
var classDecl = SyntaxFactory.ClassDeclaration("Person")
    .WithModifiers(SyntaxFactory.TokenList(
        SyntaxFactory.Token(SyntaxKind.PublicKeyword)));

// 生成: publicclass Person  // 缺少空格

常见问题

Q1: 语法树和 AST 有什么区别?

A: Roslyn 的语法树比传统的 AST(抽象语法树)更完整:

特性传统 ASTRoslyn 语法树
空格丢失保留
注释丢失保留
格式丢失保留
错误停止解析继续解析
用途编译编译 + 工具

Roslyn 的语法树是"无损"的,可以完全重建原始源代码。

Q2: 如何处理语法错误?

A: Roslyn 会继续解析,并在语法树中标记错误:

csharp
var code = @"
public class Person
{
    public string Name { get; set; }
    public int Age { get set; }  // 语法错误:缺少 ;
}";

var tree = CSharpSyntaxTree.ParseText(code);

// 检查诊断信息
var diagnostics = tree.GetDiagnostics();
foreach (var diagnostic in diagnostics)
{
    Console.WriteLine($"{diagnostic.Severity}: {diagnostic.GetMessage()}");
    Console.WriteLine($"Location: {diagnostic.Location.GetLineSpan()}");
}

// 输出:
// Error: ; expected
// Location: (5,23)-(5,23)

// 语法树仍然可用
var root = tree.GetRoot();
var classes = root.DescendantNodes().OfType<ClassDeclarationSyntax>();
Console.WriteLine($"Found {classes.Count()} classes");
// 输出: Found 1 classes

Q3: 语义模型的生命周期是什么?

A: 语义模型与编译对象绑定:

csharp
// 创建编译
var compilation = CSharpCompilation.Create("Test")
    .AddSyntaxTrees(tree);

// 获取语义模型
var semanticModel = compilation.GetSemanticModel(tree);

// 语义模型缓存在编译对象中
var semanticModel2 = compilation.GetSemanticModel(tree);
// semanticModel == semanticModel2 (同一个对象)

// 修改编译会使语义模型失效
var newCompilation = compilation.AddSyntaxTrees(anotherTree);
var newSemanticModel = newCompilation.GetSemanticModel(tree);
// newSemanticModel != semanticModel (不同的对象)

Q4: 如何在多个文件之间共享符号?

A: 符号在编译对象中共享:

csharp
var file1 = @"
namespace MyNamespace
{
    public class Person { }
}";

var file2 = @"
namespace MyNamespace
{
    public class Employee : Person { }
}";

var tree1 = CSharpSyntaxTree.ParseText(file1);
var tree2 = CSharpSyntaxTree.ParseText(file2);

// 创建包含两个文件的编译
var compilation = CSharpCompilation.Create("Test")
    .AddSyntaxTrees(tree1, tree2);

// 在 file2 中可以访问 file1 中定义的 Person
var semanticModel2 = compilation.GetSemanticModel(tree2);
var employeeClass = tree2.GetRoot()
    .DescendantNodes()
    .OfType<ClassDeclarationSyntax>()
    .First();

var employeeSymbol = semanticModel2.GetDeclaredSymbol(employeeClass);
var baseType = employeeSymbol.BaseType;

Console.WriteLine($"Base Type: {baseType.Name}");
// 输出: Base Type: Person

Q5: Roslyn 支持哪些语言?

A: Roslyn 主要支持:

  1. C#

    • 完整支持
    • 最新语言特性
    • 源生成器支持
  2. Visual Basic

    • 完整支持
    • 维护模式
    • 有限的新特性
  3. F#

    • 使用独立的编译器
    • 不是 Roslyn 的一部分

下一步

现在你已经了解了 Roslyn 的基础知识!接下来可以:

深入学习核心概念

查看实际示例

API 参考

工具和资源

🔗 相关资源

深入学习

API 参考

实战示例


最后更新: 2025-01-21

Roslyn 性能特性

编译器性能优化

Roslyn 编译器采用了多种优化技术来提高编译速度:

1. 增量编译

csharp
// Roslyn 只重新编译变化的文件
// 未变化的文件使用缓存的结果

// 示例:修改一个文件
// File1.cs - 未修改 → 使用缓存
// File2.cs - 已修改 → 重新编译
// File3.cs - 未修改 → 使用缓存

优势

  • ✅ 大幅减少编译时间
  • ✅ 提高开发效率
  • ✅ 降低 CPU 和内存使用

2. 并行编译

Roslyn 可以并行编译多个文件:

csharp
// 配置并行编译
<PropertyGroup>
  <UseSharedCompilation>true</UseSharedCompilation>
  <BuildInParallel>true</BuildInParallel>
</PropertyGroup>

3. 共享编译服务器

csharp
// VBCSCompiler.exe 作为后台服务
// 多个编译任务共享同一个编译器实例
// 减少启动开销和内存占用

// 查看编译服务器状态
dotnet build-server shutdown

内存管理

Roslyn 使用高效的内存管理策略:

不可变数据结构

csharp
// 语法树节点是不可变的
var oldNode = syntaxTree.GetRoot();
var newNode = oldNode.ReplaceNode(targetNode, newTargetNode);

// oldNode 仍然有效,可以安全共享
// newNode 是新的树,共享未修改的部分

优势

  • ✅ 线程安全
  • ✅ 结构共享,节省内存
  • ✅ 支持撤销/重做
  • ✅ 简化并发编程

对象池

csharp
// Roslyn 使用对象池减少 GC 压力
// 频繁创建的对象被重用

// 示例:StringBuilder 池
var builder = StringBuilderPool.Allocate();
try
{
    builder.Append("code");
    return builder.ToString();
}
finally
{
    StringBuilderPool.Free(builder);
}

API 性能考虑

使用 Roslyn API 时的性能建议:

✅ 推荐做法

csharp
// 1. 缓存 SemanticModel
var semanticModel = compilation.GetSemanticModel(syntaxTree);
foreach (var node in nodes)
{
    var symbol = semanticModel.GetSymbolInfo(node).Symbol;
    // 重用 semanticModel
}

// 2. 使用 SyntaxWalker 而不是递归
public class MyWalker : SyntaxWalker
{
    public override void VisitClassDeclaration(ClassDeclarationSyntax node)
    {
        // 高效遍历
        base.VisitClassDeclaration(node);
    }
}

// 3. 使用 SymbolEqualityComparer
var comparer = SymbolEqualityComparer.Default;
if (comparer.Equals(symbol1, symbol2))
{
    // 正确的符号比较
}

❌ 避免的做法

csharp
// 1. 重复创建 SemanticModel
foreach (var node in nodes)
{
    // ❌ 每次都创建新的 SemanticModel
    var semanticModel = compilation.GetSemanticModel(syntaxTree);
    var symbol = semanticModel.GetSymbolInfo(node).Symbol;
}

// 2. 使用递归遍历大型语法树
void TraverseNode(SyntaxNode node)
{
    // ❌ 可能导致栈溢出
    foreach (var child in node.ChildNodes())
    {
        TraverseNode(child);
    }
}

// 3. 使用 == 比较符号
if (symbol1 == symbol2)  // ❌ 错误的比较方式
{
    // 可能得到错误结果
}

编译时间优化

实际项目中的优化技巧:

xml
<!-- 1. 启用确定性构建 -->
<PropertyGroup>
  <Deterministic>true</Deterministic>
</PropertyGroup>

<!-- 2. 禁用不需要的分析器 -->
<PropertyGroup>
  <RunAnalyzersDuringBuild>false</RunAnalyzersDuringBuild>
  <RunAnalyzersDuringLiveAnalysis>true</RunAnalyzersDuringLiveAnalysis>
</PropertyGroup>

<!-- 3. 使用增量源生成器 -->
<ItemGroup>
  <PackageReference Include="MyGenerator" Version="1.0.0" 
                    OutputItemType="Analyzer" />
</ItemGroup>

<!-- 4. 配置并行度 -->
<PropertyGroup>
  <MaxCpuCount>0</MaxCpuCount>  <!-- 使用所有 CPU -->
</PropertyGroup>

性能监控

监控编译性能的工具:

bash
# 1. 详细构建日志
dotnet build -v detailed

# 2. 二进制日志
dotnet build -bl:build.binlog

# 3. 使用 MSBuild Structured Log Viewer
# 下载:https://msbuildlog.com/

# 4. 性能分析
dotnet build -p:ReportAnalyzer=true

Roslyn 扩展性

分析器 (Analyzers)

分析器可以检测代码问题:

csharp
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class MyAnalyzer : DiagnosticAnalyzer
{
    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics 
        => ImmutableArray.Create(Rule);
    
    private static readonly DiagnosticDescriptor Rule = new(
        "MY001",
        "Title",
        "Message",
        "Category",
        DiagnosticSeverity.Warning,
        isEnabledByDefault: true);
    
    public override void Initialize(AnalysisContext context)
    {
        context.RegisterSyntaxNodeAction(AnalyzeNode, 
            SyntaxKind.ClassDeclaration);
    }
    
    private void AnalyzeNode(SyntaxNodeAnalysisContext context)
    {
        var classDecl = (ClassDeclarationSyntax)context.Node;
        
        // 分析逻辑
        if (HasIssue(classDecl))
        {
            context.ReportDiagnostic(Diagnostic.Create(
                Rule, classDecl.GetLocation()));
        }
    }
}

代码修复 (Code Fixes)

代码修复可以自动修复问题:

csharp
[ExportCodeFixProvider(LanguageNames.CSharp)]
public class MyCodeFixProvider : CodeFixProvider
{
    public override ImmutableArray<string> FixableDiagnosticIds 
        => ImmutableArray.Create("MY001");
    
    public override async Task RegisterCodeFixesAsync(
        CodeFixContext context)
    {
        var root = await context.Document
            .GetSyntaxRootAsync(context.CancellationToken);
        
        var diagnostic = context.Diagnostics.First();
        var diagnosticSpan = diagnostic.Location.SourceSpan;
        
        var node = root.FindNode(diagnosticSpan);
        
        context.RegisterCodeFix(
            CodeAction.Create(
                "Fix issue",
                c => FixIssueAsync(context.Document, node, c),
                equivalenceKey: "FixIssue"),
            diagnostic);
    }
    
    private async Task<Document> FixIssueAsync(
        Document document, 
        SyntaxNode node, 
        CancellationToken cancellationToken)
    {
        // 修复逻辑
        var newNode = /* 创建修复后的节点 */;
        var root = await document.GetSyntaxRootAsync(cancellationToken);
        var newRoot = root.ReplaceNode(node, newNode);
        
        return document.WithSyntaxRoot(newRoot);
    }
}

重构 (Refactorings)

重构提供代码转换功能:

csharp
[ExportCodeRefactoringProvider(LanguageNames.CSharp)]
public class MyRefactoringProvider : CodeRefactoringProvider
{
    public override async Task ComputeRefactoringsAsync(
        CodeRefactoringContext context)
    {
        var root = await context.Document
            .GetSyntaxRootAsync(context.CancellationToken);
        
        var node = root.FindNode(context.Span);
        
        if (node is ClassDeclarationSyntax classDecl)
        {
            context.RegisterRefactoring(
                CodeAction.Create(
                    "Convert to record",
                    c => ConvertToRecordAsync(
                        context.Document, classDecl, c),
                    equivalenceKey: "ConvertToRecord"));
        }
    }
    
    private async Task<Document> ConvertToRecordAsync(
        Document document,
        ClassDeclarationSyntax classDecl,
        CancellationToken cancellationToken)
    {
        // 转换逻辑
        var recordDecl = /* 创建 record 声明 */;
        var root = await document.GetSyntaxRootAsync(cancellationToken);
        var newRoot = root.ReplaceNode(classDecl, recordDecl);
        
        return document.WithSyntaxRoot(newRoot);
    }
}

基于 MIT 许可发布