Skip to content

编译流程

Roslyn 编译流程

编译阶段详解

1. 词法分析 (Lexical Analysis)

将源代码文本分解为标记(Tokens)。

输入:

csharp
public class Person { }

输出:

  • public (关键字)
  • class (关键字)
  • Person (标识符)
  • { (符号)
  • } (符号)

2. 语法分析 (Syntax Analysis)

将标记组织成语法树。

输出:

ClassDeclaration
├── Modifiers: public
├── Keyword: class
├── Identifier: Person
└── Members: (empty)

3. 语义分析 (Semantic Analysis)

进行类型检查和符号解析。

功能:

  • 类型检查
  • 名称解析
  • 重载解析
  • 类型推断

4. 源生成器执行

这是源生成器介入的时机!

5. IL 代码生成

将语义模型转换为中间语言(IL)代码。

6. 程序集生成

生成最终的 .dll 或 .exe 文件。

源生成器的位置

源生成器在语义分析之后、IL 生成之前执行:

源代码 → 语法树 → 语义模型 → [源生成器] → 新源文件 → 重新分析 → IL 代码

为什么在这个位置?

  1. ✅ 可以访问完整的类型信息
  2. ✅ 可以查询符号和语义
  3. ✅ 生成的代码会被重新编译
  4. ✅ 生成的代码可以被其他代码使用

增量编译

现代编译器使用增量编译来提高性能:

增量生成器利用这一机制来优化性能。

实际示例

编译一个简单的类

csharp
public class Calculator
{
    public int Add(int a, int b) => a + b;
}

编译步骤:

  1. 词法分析 → 生成 tokens
  2. 语法分析 → 构建语法树
  3. 语义分析 → 解析类型、方法签名
  4. 源生成器 → (如果有)生成额外代码
  5. IL 生成 → 生成 IL 指令
  6. 程序集 → 输出 .dll

下一步

编译阶段状态转换

编译状态机

各阶段的输入输出

阶段输入输出主要操作
词法分析源代码文本Token 流分词、识别关键字
语法分析Token 流语法树构建 AST、检查语法
语义分析语法树 + 引用语义模型类型检查、符号解析
源生成语义模型新源文件分析代码、生成代码
IL 生成语义模型IL 代码生成中间语言
程序集生成IL 代码.dll/.exe输出最终文件

源生成器执行时机详解

详细的执行流程

源生成器的两个阶段

阶段 1: Initialize(初始化)

csharp
public void Initialize(GeneratorInitializationContext context)
{
    // 1. 注册语法接收器(可选)
    context.RegisterForSyntaxNotifications(() => new MySyntaxReceiver());
    
    // 2. 注册后处理(可选)
    context.RegisterForPostInitialization(ctx =>
    {
        // 生成全局代码,如特性定义
        ctx.AddSource("Attributes.g.cs", attributeCode);
    });
}

执行时机:编译开始时,只执行一次

用途

  • 注册语法接收器
  • 生成全局代码(如特性定义)
  • 设置生成器配置

阶段 2: Execute(执行)

csharp
public void Execute(GeneratorExecutionContext context)
{
    // 1. 获取编译信息
    var compilation = context.Compilation;
    
    // 2. 获取语法接收器收集的节点
    if (context.SyntaxReceiver is MySyntaxReceiver receiver)
    {
        // 3. 处理每个候选节点
        foreach (var classDecl in receiver.CandidateClasses)
        {
            var semanticModel = compilation.GetSemanticModel(classDecl.SyntaxTree);
            var symbol = semanticModel.GetDeclaredSymbol(classDecl);
            
            // 4. 生成代码
            var code = GenerateCode(symbol);
            context.AddSource($"{symbol.Name}.g.cs", code);
        }
    }
    
    // 5. 报告诊断(可选)
    context.ReportDiagnostic(/* ... */);
}

执行时机:语义分析完成后

用途

  • 分析代码
  • 生成新源文件
  • 报告诊断信息

源生成器可以访问的信息

csharp
public void Execute(GeneratorExecutionContext context)
{
    // ✅ 可以访问:编译信息
    var compilation = context.Compilation;
    
    // ✅ 可以访问:所有语法树
    foreach (var tree in compilation.SyntaxTrees)
    {
        // 处理...
    }
    
    // ✅ 可以访问:语义模型
    var semanticModel = compilation.GetSemanticModel(tree);
    
    // ✅ 可以访问:符号信息
    var symbol = semanticModel.GetDeclaredSymbol(node);
    
    // ✅ 可以访问:类型信息
    var typeInfo = semanticModel.GetTypeInfo(expression);
    
    // ✅ 可以访问:引用的程序集
    var references = compilation.References;
    
    // ✅ 可以访问:分析器配置
    context.AnalyzerConfigOptions.GlobalOptions
        .TryGetValue("build_property.RootNamespace", out var ns);
    
    // ❌ 不能访问:文件系统
    // File.ReadAllText("config.json");  // 不可靠
    
    // ❌ 不能访问:网络
    // HttpClient.GetAsync("...");  // 不允许
    
    // ❌ 不能修改:现有代码
    // 只能添加新文件,不能修改现有文件
}

调试编译过程

方法 1: 使用诊断输出

csharp
public void Execute(GeneratorExecutionContext context)
{
    // 输出调试信息
    context.ReportDiagnostic(Diagnostic.Create(
        new DiagnosticDescriptor(
            "SG0001",
            "Debug Info",
            "Compilation has {0} syntax trees",
            "SourceGenerator",
            DiagnosticSeverity.Info,
            true),
        Location.None,
        context.Compilation.SyntaxTrees.Count()));
}

方法 2: 使用 Debugger.Launch()

csharp
public void Execute(GeneratorExecutionContext context)
{
    #if DEBUG
    if (!Debugger.IsAttached)
    {
        Debugger.Launch();  // 弹出调试器选择对话框
    }
    #endif
    
    // 生成器逻辑...
}

方法 3: 附加到编译器进程

  1. 在 Visual Studio 中打开生成器项目
  2. 在生成器代码中设置断点
  3. 选择 调试附加到进程
  4. 找到 csc.exeVBCSCompiler.exe
  5. 附加调试器
  6. 在使用者项目中触发编译

方法 4: 查看生成的文件

bash
# 查看生成的文件位置
dir obj\Debug\net8.0\generated /s

# 查看生成的文件内容
type obj\Debug\net8.0\generated\MyGenerator\MyGenerator.MyGenerator\Generated.g.cs

调试技巧

csharp
public void Execute(GeneratorExecutionContext context)
{
    try
    {
        // 记录开始时间
        var sw = Stopwatch.StartNew();
        
        // 生成器逻辑
        var code = GenerateCode(context);
        
        // 记录结束时间
        sw.Stop();
        
        // 输出性能信息
        context.ReportDiagnostic(Diagnostic.Create(
            new DiagnosticDescriptor(
                "SG0002",
                "Performance",
                "Generation took {0}ms, generated {1} characters",
                "SourceGenerator",
                DiagnosticSeverity.Info,
                true),
            Location.None,
            sw.ElapsedMilliseconds,
            code.Length));
        
        context.AddSource("Generated.g.cs", code);
    }
    catch (Exception ex)
    {
        // 捕获并报告异常
        context.ReportDiagnostic(Diagnostic.Create(
            new DiagnosticDescriptor(
                "SG0003",
                "Error",
                "Generation failed: {0}",
                "SourceGenerator",
                DiagnosticSeverity.Error,
                true),
            Location.None,
            ex.Message));
    }
}

性能优化

编译性能影响

优化策略

1. 使用增量生成器

csharp
// ✅ 推荐:增量生成器
[Generator]
public class MyIncrementalGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // 定义增量管道
        var classDeclarations = context.SyntaxProvider
            .CreateSyntaxProvider(
                predicate: static (s, _) => s is ClassDeclarationSyntax c && 
                                           c.AttributeLists.Count > 0,
                transform: static (ctx, _) => GetSemanticTarget(ctx))
            .Where(static m => m is not null);
        
        context.RegisterSourceOutput(classDeclarations,
            static (spc, source) => Execute(source, spc));
    }
}

// ❌ 避免:传统生成器
[Generator]
public class MyGenerator : ISourceGenerator
{
    public void Execute(GeneratorExecutionContext context)
    {
        // 每次编译都完全执行
        foreach (var tree in context.Compilation.SyntaxTrees)
        {
            // 处理所有文件...
        }
    }
}

2. 提前过滤

csharp
// ✅ 推荐:使用 SyntaxReceiver 提前过滤
class MySyntaxReceiver : ISyntaxReceiver
{
    public List<ClassDeclarationSyntax> CandidateClasses { get; } = new();
    
    public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
    {
        // 只收集带有特性的类
        if (syntaxNode is ClassDeclarationSyntax classDecl &&
            classDecl.AttributeLists.Count > 0)
        {
            CandidateClasses.Add(classDecl);
        }
    }
}

// ❌ 避免:在 Execute 中遍历所有节点
public void Execute(GeneratorExecutionContext context)
{
    foreach (var tree in context.Compilation.SyntaxTrees)
    {
        foreach (var node in tree.GetRoot().DescendantNodes())
        {
            // 处理所有节点...
        }
    }
}

3. 缓存计算结果

csharp
// ✅ 推荐:缓存重复计算
private static readonly ConcurrentDictionary<string, string> _cache = new();

public void Execute(GeneratorExecutionContext context)
{
    foreach (var classSymbol in GetClassesToGenerate(context))
    {
        var key = classSymbol.ToDisplayString();
        
        var code = _cache.GetOrAdd(key, _ => GenerateCode(classSymbol));
        
        context.AddSource($"{classSymbol.Name}.g.cs", code);
    }
}

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

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

foreach (var classDecl in candidates)
{
    var symbol = semanticModel.GetDeclaredSymbol(classDecl);  // 只对候选类进行语义分析
    if (HasTargetAttribute(symbol))
    {
        GenerateCode(symbol);
    }
}

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

实际示例

示例 1: 跟踪编译过程

csharp
[Generator]
public class CompilationTrackerGenerator : ISourceGenerator
{
    public void Initialize(GeneratorInitializationContext context)
    {
        context.RegisterForPostInitialization(ctx =>
        {
            Log(ctx, "PostInitialization: 生成全局代码");
            
            ctx.AddSource("CompilationInfo.g.cs", $$"""
                // 编译时间: {{DateTime.Now}}
                public static class CompilationInfo
                {
                    public const string CompilationTime = "{{DateTime.Now:yyyy-MM-dd HH:mm:ss}}";
                }
                """);
        });
    }
    
    public void Execute(GeneratorExecutionContext context)
    {
        Log(context, $"Execute: 开始执行,共 {context.Compilation.SyntaxTrees.Count()} 个文件");
        
        var classes = 0;
        var methods = 0;
        
        foreach (var tree in context.Compilation.SyntaxTrees)
        {
            var root = tree.GetRoot();
            classes += root.DescendantNodes().OfType<ClassDeclarationSyntax>().Count();
            methods += root.DescendantNodes().OfType<MethodDeclarationSyntax>().Count();
        }
        
        Log(context, $"Execute: 找到 {classes} 个类,{methods} 个方法");
        
        context.AddSource("Statistics.g.cs", $$"""
            public static class Statistics
            {
                public const int TotalClasses = {{classes}};
                public const int TotalMethods = {{methods}};
            }
            """);
        
        Log(context, "Execute: 完成");
    }
    
    private void Log(GeneratorExecutionContext context, string message)
    {
        context.ReportDiagnostic(Diagnostic.Create(
            new DiagnosticDescriptor(
                "SG0001",
                "Compilation Tracker",
                message,
                "SourceGenerator",
                DiagnosticSeverity.Info,
                true),
            Location.None));
    }
}

示例 2: 条件编译

csharp
[Generator]
public class ConditionalGenerator : ISourceGenerator
{
    public void Execute(GeneratorExecutionContext context)
    {
        // 检查编译配置
        var isDebug = context.Compilation.Options.OptimizationLevel == 
                      OptimizationLevel.Debug;
        
        // 检查预处理符号
        var hasDebugSymbol = context.ParseOptions
            .PreprocessorSymbolNames
            .Contains("DEBUG");
        
        // 根据配置生成不同的代码
        var code = isDebug ? GenerateDebugCode() : GenerateReleaseCode();
        
        context.AddSource("Conditional.g.cs", code);
    }
    
    private string GenerateDebugCode()
    {
        return """
            public static class BuildInfo
            {
                public const string Configuration = "Debug";
                public const bool EnableLogging = true;
                public const bool EnableAssertions = true;
            }
            """;
    }
    
    private string GenerateReleaseCode()
    {
        return """
            public static class BuildInfo
            {
                public const string Configuration = "Release";
                public const bool EnableLogging = false;
                public const bool EnableAssertions = false;
            }
            """;
    }
}

常见问题

Q1: 源生成器会影响编译速度吗?

A: 会有一定影响,但可以优化:

  • 传统生成器:增加 5-20% 编译时间
  • 增量生成器:增加 1-5% 编译时间
  • 优化建议
    • 使用 IIncrementalGenerator
    • 使用 SyntaxReceiver 提前过滤
    • 缓存重复计算
    • 避免复杂的语义分析

Q2: 生成的代码什么时候被编译?

A: 立即编译:

1. 源生成器执行
2. 生成新源文件
3. 新源文件被添加到编译
4. 编译器重新进行语义分析
5. 所有代码(包括生成的)一起编译为 IL

Q3: 源生成器可以生成多少文件?

A: 理论上没有限制,但建议:

  • 每个类生成一个文件
  • 文件名使用 .g.cs 后缀
  • 避免生成过多小文件
  • 考虑合并相关代码
csharp
// ✅ 推荐:每个类一个文件
context.AddSource("Person.g.cs", personCode);
context.AddSource("Product.g.cs", productCode);

// ⚠️ 可以但不推荐:一个大文件
context.AddSource("AllGenerated.g.cs", allCode);

// ❌ 避免:太多小文件
for (int i = 0; i < 1000; i++)
{
    context.AddSource($"File{i}.g.cs", smallCode);
}

Q4: 如何处理生成器之间的依赖?

A: 源生成器按顺序执行,但不应该相互依赖:

csharp
// ❌ 不推荐:依赖其他生成器
[Generator]
public class DependentGenerator : ISourceGenerator
{
    public void Execute(GeneratorExecutionContext context)
    {
        // 假设另一个生成器已经生成了 BaseClass
        // 这是不可靠的!
        var code = """
            public class DerivedClass : BaseClass { }
            """;
        context.AddSource("Derived.g.cs", code);
    }
}

// ✅ 推荐:独立的生成器
[Generator]
public class IndependentGenerator : ISourceGenerator
{
    public void Execute(GeneratorExecutionContext context)
    {
        // 生成完整的、独立的代码
        var code = """
            public class BaseClass { }
            public class DerivedClass : BaseClass { }
            """;
        context.AddSource("Classes.g.cs", code);
    }
}

Q5: 源生成器可以读取项目文件吗?

A: 可以通过 AdditionalFiles:

xml
<!-- 在项目文件中 -->
<ItemGroup>
  <AdditionalFiles Include="config.json" />
</ItemGroup>
csharp
// 在生成器中
public void Execute(GeneratorExecutionContext context)
{
    var configFile = context.AdditionalFiles
        .FirstOrDefault(f => Path.GetFileName(f.Path) == "config.json");
    
    if (configFile != null)
    {
        var content = configFile.GetText()?.ToString();
        // 处理配置...
    }
}

下一步

现在你已经深入了解了编译流程和源生成器的执行时机!接下来可以:

深入学习

实践示例

API 参考

🔗 相关资源

深入学习

API 参考

实战示例


最后更新: 2025-01-21

编译优化技术

增量编译

Roslyn 使用增量编译来提高编译速度:

工作原理

  1. 文件指纹:计算每个文件的哈希值
  2. 依赖追踪:记录文件之间的依赖关系
  3. 缓存管理:存储编译结果
  4. 智能重编译:只编译变化的文件及其依赖

示例

csharp
// 项目有 100 个文件
// 修改了 File1.cs

// 传统编译:重新编译所有 100 个文件
// 增量编译:只编译 File1.cs 和依赖它的文件(可能只有 5 个)

// 编译时间对比:
// 传统:30 秒
// 增量:3 秒
// 提升 10 倍!

并行编译

Roslyn 支持并行编译多个文件:

csharp
// 配置并行编译
<PropertyGroup>
  <!-- 使用所有可用 CPU 核心 -->
  <MaxCpuCount>0</MaxCpuCount>
  
  <!-- 启用共享编译 -->
  <UseSharedCompilation>true</UseSharedCompilation>
  
  <!-- 并行构建项目 -->
  <BuildInParallel>true</BuildInParallel>
</PropertyGroup>

性能提升

单核编译:60 秒
4 核并行:18 秒(提升 3.3 倍)
8 核并行:12 秒(提升 5 倍)

编译缓存

Roslyn 使用多级缓存策略:

缓存位置

bash
# Windows
%LOCALAPPDATA%\Microsoft\VisualStudio\RoslynCache

# Linux/Mac
~/.local/share/Microsoft/VisualStudio/RoslynCache

编译诊断和调试

查看编译详情

使用详细日志查看编译过程:

bash
# 详细输出
dotnet build -v detailed

# 诊断输出(最详细)
dotnet build -v diagnostic

# 二进制日志(推荐)
dotnet build -bl:build.binlog

分析二进制日志

使用 MSBuild Structured Log Viewer:

bash
# 1. 生成二进制日志
dotnet build -bl

# 2. 下载查看器
# https://msbuildlog.com/

# 3. 打开 msbuild.binlog 文件
# 可以看到:
# - 每个任务的执行时间
# - 源生成器的执行情况
# - 编译错误和警告
# - 文件依赖关系

调试编译过程

方法 1: 使用环境变量

bash
# Windows
set ROSLYN_COMPILER_DEBUG=1
dotnet build

# Linux/Mac
export ROSLYN_COMPILER_DEBUG=1
dotnet build

方法 2: 附加调试器

csharp
// 在源生成器中添加
public void Execute(GeneratorExecutionContext context)
{
    #if DEBUG
    if (!System.Diagnostics.Debugger.IsAttached)
    {
        System.Diagnostics.Debugger.Launch();
    }
    #endif
    
    // 生成器逻辑...
}

方法 3: 使用诊断输出

csharp
public void Execute(GeneratorExecutionContext context)
{
    // 输出诊断信息
    context.ReportDiagnostic(Diagnostic.Create(
        new DiagnosticDescriptor(
            "SG9999",
            "Debug Info",
            "Compilation: {0}, SyntaxTrees: {1}",
            "Debug",
            DiagnosticSeverity.Info,
            true),
        Location.None,
        context.Compilation.AssemblyName,
        context.Compilation.SyntaxTrees.Count()));
}

编译错误处理

常见编译错误

1. 语法错误

csharp
// 错误代码
public class Person
{
    public string Name { get; set; }
    // 缺少右花括号

错误信息

CS1513: } expected

解决方法:添加缺失的花括号

2. 类型错误

csharp
// 错误代码
string number = 123;  // 类型不匹配

错误信息

CS0029: Cannot implicitly convert type 'int' to 'string'

解决方法

csharp
string number = 123.ToString();  // 显式转换

3. 命名空间错误

csharp
// 错误代码
var list = new List<string>();  // 缺少 using

错误信息

CS0246: The type or namespace name 'List<>' could not be found

解决方法

csharp
using System.Collections.Generic;

var list = new List<string>();

源生成器错误

1. 生成器未执行

症状:生成的代码不存在

检查清单

xml
<!-- 1. 检查项目引用 -->
<ItemGroup>
  <ProjectReference Include="..\MyGenerator\MyGenerator.csproj" 
                    OutputItemType="Analyzer"  <!-- 必须 -->
                    ReferenceOutputAssembly="false" />  <!-- 必须 -->
</ItemGroup>

<!-- 2. 检查生成器特性 -->
[Generator]  // 必须有此特性
public class MyGenerator : ISourceGenerator { }

<!-- 3. 清理并重建 -->
dotnet clean
dotnet build

2. 生成的代码有错误

症状:编译失败,错误指向生成的代码

调试步骤

bash
# 1. 查看生成的文件
type obj\Debug\net8.0\generated\MyGenerator\*.g.cs

# 2. 检查生成逻辑
# 在生成器中添加日志

# 3. 验证生成的代码语法
# 使用在线 C# 编译器测试

3. 性能问题

症状:编译时间显著增加

优化方法

csharp
// ✅ 使用增量生成器
[Generator]
public class MyGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // 增量管道
    }
}

// ✅ 使用 SyntaxReceiver 过滤
class MySyntaxReceiver : ISyntaxReceiver
{
    public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
    {
        // 只收集需要的节点
        if (syntaxNode is ClassDeclarationSyntax classDecl &&
            classDecl.AttributeLists.Count > 0)
        {
            // 收集...
        }
    }
}

// ✅ 缓存计算结果
private static readonly Dictionary<string, string> _cache = new();

public string GenerateCode(string key)
{
    if (_cache.TryGetValue(key, out var cached))
    {
        return cached;
    }
    
    var result = /* 生成代码 */;
    _cache[key] = result;
    return result;
}

编译配置选项

项目文件配置

xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <!-- 基本配置 -->
    <TargetFramework>net8.0</TargetFramework>
    <LangVersion>latest</LangVersion>
    <Nullable>enable</Nullable>
    
    <!-- 编译优化 -->
    <Optimize>true</Optimize>
    <DebugType>portable</DebugType>
    <DebugSymbols>true</DebugSymbols>
    
    <!-- 确定性构建 -->
    <Deterministic>true</Deterministic>
    <ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
    
    <!-- 并行编译 -->
    <UseSharedCompilation>true</UseSharedCompilation>
    <BuildInParallel>true</BuildInParallel>
    
    <!-- 分析器配置 -->
    <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
    <AnalysisLevel>latest</AnalysisLevel>
    
    <!-- 警告配置 -->
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
    <WarningLevel>5</WarningLevel>
    
    <!-- 输出配置 -->
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
    <ProduceReferenceAssembly>true</ProduceReferenceAssembly>
  </PropertyGroup>

</Project>

编译器指令

csharp
// 条件编译
#if DEBUG
    Console.WriteLine("Debug mode");
#elif RELEASE
    Console.WriteLine("Release mode");
#endif

// 警告控制
#pragma warning disable CS0168  // 禁用特定警告
var unused = 0;
#pragma warning restore CS0168  // 恢复警告

// 可空性控制
#nullable enable
string? nullableString = null;
#nullable disable

// 区域标记
#region Helper Methods
private void Helper() { }
#endregion

// 行号指令(用于生成的代码)
#line 100 "OriginalFile.cs"
// 错误将指向 OriginalFile.cs 的第 100 行
#line default

全局配置

使用 .editorconfig 配置编译器行为:

ini
# .editorconfig
root = true

[*.cs]
# 语言版本
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async

# 代码风格
csharp_style_var_for_built_in_types = true
csharp_style_var_when_type_is_apparent = true

# 格式化
csharp_new_line_before_open_brace = all
csharp_indent_case_contents = true

# 分析器严重性
dotnet_diagnostic.CA1001.severity = error
dotnet_diagnostic.CA1031.severity = warning

# 源生成器配置
build_property.MyGenerator_EnableLogging = true
build_property.MyGenerator_OutputPath = Generated

编译性能分析

性能指标

关键性能指标:

1. 编译时间
   - 冷编译(首次):完整编译所有文件
   - 热编译(增量):只编译变化的文件
   
2. 内存使用
   - 峰值内存:编译过程中的最大内存占用
   - 平均内存:编译过程中的平均内存占用
   
3. CPU 使用
   - CPU 时间:实际 CPU 计算时间
   - 并行度:同时运行的编译任务数
   
4. 磁盘 I/O
   - 读取:读取源文件和依赖
   - 写入:写入输出文件和缓存

性能测试

bash
# 1. 清理项目
dotnet clean

# 2. 冷编译测试
Measure-Command { dotnet build }

# 3. 热编译测试(修改一个文件后)
Measure-Command { dotnet build }

# 4. 并行编译测试
Measure-Command { dotnet build -m }

# 5. 生成性能报告
dotnet build -p:ReportAnalyzer=true

性能优化建议

具体优化措施

  1. 优化源生成器

    csharp
    // ✅ 使用增量生成器
    [Generator]
    public class MyGenerator : IIncrementalGenerator { }
    
    // ✅ 提前过滤
    var candidates = context.SyntaxProvider
        .CreateSyntaxProvider(
            predicate: static (s, _) => s is ClassDeclarationSyntax,
            transform: static (ctx, _) => GetTarget(ctx))
        .Where(static m => m is not null);
  2. 优化项目结构

    xml
    <!-- ✅ 使用项目引用而不是程序集引用 -->
    <ItemGroup>
      <ProjectReference Include="..\Shared\Shared.csproj" />
    </ItemGroup>
    
    <!-- ✅ 启用引用程序集 -->
    <PropertyGroup>
      <ProduceReferenceAssembly>true</ProduceReferenceAssembly>
    </PropertyGroup>
  3. 配置分析器

    xml
    <!-- ✅ 只在需要时运行分析器 -->
    <PropertyGroup>
      <RunAnalyzersDuringBuild>false</RunAnalyzersDuringBuild>
      <RunAnalyzersDuringLiveAnalysis>true</RunAnalyzersDuringLiveAnalysis>
    </PropertyGroup>

基于 MIT 许可发布