Skip to content

编译 API 高级

⏱️ 30-45 分钟 | 📚 高级级别

🎯 学习目标

完成本指南后,你将能够:

  • [ ] 实现跨程序集符号查找
  • [ ] 掌握增量编译技术
  • [ ] 进行编译性能优化
  • [ ] 处理复杂的编译场景
  • [ ] 实现高级诊断分析

📖 前置知识

在开始之前,你应该:

🔧 高级技术

技术 1:跨程序集符号查找

用途: 在多个程序集中查找和解析类型符号

何时使用: 需要分析依赖关系或查找外部类型时

示例:

csharp
using Microsoft.CodeAnalysis;
using System.Collections.Generic;
using System.Linq;

public class CrossAssemblySymbolLookup
{
    // 在所有引用的程序集中查找类型
    public INamedTypeSymbol FindTypeAcrossAssemblies(
        Compilation compilation,
        string fullyQualifiedName)
    {
        // 方法 1: 使用 GetTypeByMetadataName(最快)
        var type = compilation.GetTypeByMetadataName(fullyQualifiedName);
        if (type != null)
        {
            return type;
        }
        
        // 方法 2: 手动搜索所有程序集
        foreach (var reference in compilation.References)
        {
            var assembly = compilation.GetAssemblyOrModuleSymbol(reference) 
                as IAssemblySymbol;
            
            if (assembly != null)
            {
                type = FindTypeInAssembly(assembly, fullyQualifiedName);
                if (type != null)
                {
                    return type;
                }
            }
        }
        
        return null;
    }
    
    // 在特定程序集中查找类型
    private INamedTypeSymbol FindTypeInAssembly(
        IAssemblySymbol assembly,
        string typeName)
    {
        return FindTypeInNamespace(assembly.GlobalNamespace, typeName);
    }
    
    // 在命名空间中递归查找类型
    private INamedTypeSymbol FindTypeInNamespace(
        INamespaceSymbol namespaceSymbol,
        string typeName)
    {
        // 提取类型名称(不含命名空间)
        var parts = typeName.Split('.');
        var simpleTypeName = parts[^1];
        
        // 在当前命名空间中查找
        var type = namespaceSymbol.GetTypeMembers(simpleTypeName).FirstOrDefault();
        if (type != null && type.ToDisplayString() == typeName)
        {
            return type;
        }
        
        // 在子命名空间中递归查找
        foreach (var childNamespace in namespaceSymbol.GetNamespaceMembers())
        {
            type = FindTypeInNamespace(childNamespace, typeName);
            if (type != null)
            {
                return type;
            }
        }
        
        return null;
    }
    
    // 查找实现特定接口的所有类型
    public List<INamedTypeSymbol> FindTypesImplementingInterface(
        Compilation compilation,
        string interfaceName)
    {
        var results = new List<INamedTypeSymbol>();
        var interfaceType = compilation.GetTypeByMetadataName(interfaceName);
        
        if (interfaceType == null)
        {
            return results;
        }
        
        // 遍历所有程序集
        foreach (var reference in compilation.References)
        {
            var assembly = compilation.GetAssemblyOrModuleSymbol(reference) 
                as IAssemblySymbol;
            
            if (assembly != null)
            {
                var types = GetAllTypesFromAssembly(assembly);
                results.AddRange(types.Where(t => 
                    t.AllInterfaces.Contains(interfaceType, SymbolEqualityComparer.Default)));
            }
        }
        
        return results;
    }
    
    // 获取程序集中的所有类型
    private IEnumerable<INamedTypeSymbol> GetAllTypesFromAssembly(
        IAssemblySymbol assembly)
    {
        return GetAllTypesFromNamespace(assembly.GlobalNamespace);
    }
    
    // 从命名空间递归获取所有类型
    private IEnumerable<INamedTypeSymbol> GetAllTypesFromNamespace(
        INamespaceSymbol namespaceSymbol)
    {
        foreach (var type in namespaceSymbol.GetTypeMembers())
        {
            yield return type;
            
            // 递归获取嵌套类型
            foreach (var nestedType in GetNestedTypes(type))
            {
                yield return nestedType;
            }
        }
        
        foreach (var childNamespace in namespaceSymbol.GetNamespaceMembers())
        {
            foreach (var type in GetAllTypesFromNamespace(childNamespace))
            {
                yield return type;
            }
        }
    }
    
    // 获取嵌套类型
    private IEnumerable<INamedTypeSymbol> GetNestedTypes(INamedTypeSymbol type)
    {
        foreach (var nestedType in type.GetTypeMembers())
        {
            yield return nestedType;
            
            foreach (var deeperNested in GetNestedTypes(nestedType))
            {
                yield return deeperNested;
            }
        }
    }
}

关键要点:

  • GetTypeByMetadataName() 是最快的查找方式
  • 需要提供完全限定名(包括命名空间)
  • 泛型类型需要添加 `N 后缀(N 是类型参数数量)
  • 手动搜索适用于复杂查找场景

性能考虑:

  • 优先使用 GetTypeByMetadataName()
  • 缓存查找结果避免重复搜索
  • 使用并行处理加速大规模搜索

技术 2:增量编译

用途: 在不重新创建整个编译的情况下更新部分代码

何时使用: 需要频繁更新代码并保持编译状态时

示例:

csharp
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using System.Collections.Generic;

public class IncrementalCompilationManager
{
    private CSharpCompilation _currentCompilation;
    private Dictionary<string, SyntaxTree> _syntaxTreeCache = new();
    
    // 初始化基础编译
    public void Initialize(
        string assemblyName,
        IEnumerable<MetadataReference> references)
    {
        _currentCompilation = CSharpCompilation.Create(
            assemblyName: assemblyName,
            references: references,
            options: new CSharpCompilationOptions(
                OutputKind.DynamicallyLinkedLibrary));
    }
    
    // 添加新文件
    public CSharpCompilation AddFile(string fileName, string sourceCode)
    {
        var syntaxTree = CSharpSyntaxTree.ParseText(
            sourceCode, 
            path: fileName);
        
        _syntaxTreeCache[fileName] = syntaxTree;
        _currentCompilation = _currentCompilation.AddSyntaxTrees(syntaxTree);
        
        return _currentCompilation;
    }
    
    // 更新现有文件
    public CSharpCompilation UpdateFile(string fileName, string sourceCode)
    {
        if (!_syntaxTreeCache.TryGetValue(fileName, out var oldTree))
        {
            // 文件不存在,添加新文件
            return AddFile(fileName, sourceCode);
        }
        
        // 解析新代码
        var newTree = CSharpSyntaxTree.ParseText(
            sourceCode, 
            path: fileName);
        
        // 替换旧的语法树
        _currentCompilation = _currentCompilation.ReplaceSyntaxTree(
            oldTree, 
            newTree);
        
        _syntaxTreeCache[fileName] = newTree;
        
        return _currentCompilation;
    }
    
    // 删除文件
    public CSharpCompilation RemoveFile(string fileName)
    {
        if (_syntaxTreeCache.TryGetValue(fileName, out var tree))
        {
            _currentCompilation = _currentCompilation.RemoveSyntaxTrees(tree);
            _syntaxTreeCache.Remove(fileName);
        }
        
        return _currentCompilation;
    }
    
    // 批量更新文件
    public CSharpCompilation UpdateMultipleFiles(
        Dictionary<string, string> fileUpdates)
    {
        var treesToRemove = new List<SyntaxTree>();
        var treesToAdd = new List<SyntaxTree>();
        
        foreach (var (fileName, sourceCode) in fileUpdates)
        {
            // 如果文件已存在,标记旧树为删除
            if (_syntaxTreeCache.TryGetValue(fileName, out var oldTree))
            {
                treesToRemove.Add(oldTree);
            }
            
            // 创建新树
            var newTree = CSharpSyntaxTree.ParseText(
                sourceCode, 
                path: fileName);
            treesToAdd.Add(newTree);
            _syntaxTreeCache[fileName] = newTree;
        }
        
        // 批量更新
        _currentCompilation = _currentCompilation
            .RemoveSyntaxTrees(treesToRemove)
            .AddSyntaxTrees(treesToAdd);
        
        return _currentCompilation;
    }
    
    // 获取当前编译
    public CSharpCompilation GetCurrentCompilation()
    {
        return _currentCompilation;
    }
    
    // 获取文件的语法树
    public SyntaxTree GetSyntaxTree(string fileName)
    {
        _syntaxTreeCache.TryGetValue(fileName, out var tree);
        return tree;
    }
}

关键要点:

  • 使用 ReplaceSyntaxTree() 更新单个文件
  • 批量更新时一次性调用 RemoveSyntaxTrees()AddSyntaxTrees()
  • 缓存语法树以便快速访问
  • Compilation 是不可变的,每次操作返回新实例

性能优势:

  • 避免重新解析未更改的文件
  • 保留已计算的语义信息
  • 减少内存分配

技术 3:编译性能优化

用途: 提升大型项目的编译性能

何时使用: 编译时间成为瓶颈时

示例:

csharp
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

public class CompilationPerformanceOptimizer
{
    // 缓存编译对象
    private static readonly ConcurrentDictionary<string, CSharpCompilation> 
        _compilationCache = new();
    
    // 缓存元数据引用
    private static readonly ConcurrentDictionary<string, MetadataReference> 
        _referenceCache = new();
    
    // 缓存语义模型
    private static readonly ConcurrentDictionary<SyntaxTree, SemanticModel> 
        _semanticModelCache = new();
    
    // 优化 1: 缓存编译对象
    public CSharpCompilation GetOrCreateCompilation(
        string assemblyName,
        IEnumerable<SyntaxTree> syntaxTrees,
        IEnumerable<MetadataReference> references)
    {
        var cacheKey = GenerateCacheKey(assemblyName, syntaxTrees);
        
        return _compilationCache.GetOrAdd(cacheKey, _ =>
        {
            return CSharpCompilation.Create(
                assemblyName: assemblyName,
                syntaxTrees: syntaxTrees,
                references: references,
                options: new CSharpCompilationOptions(
                    OutputKind.DynamicallyLinkedLibrary));
        });
    }
    
    // 优化 2: 缓存元数据引用
    public MetadataReference GetOrCreateReference(string assemblyPath)
    {
        return _referenceCache.GetOrAdd(assemblyPath, path =>
        {
            return MetadataReference.CreateFromFile(path);
        });
    }
    
    // 优化 3: 缓存语义模型
    public SemanticModel GetOrCreateSemanticModel(
        Compilation compilation,
        SyntaxTree syntaxTree)
    {
        return _semanticModelCache.GetOrAdd(syntaxTree, tree =>
        {
            return compilation.GetSemanticModel(tree);
        });
    }
    
    // 优化 4: 并行处理语法树
    public void ProcessSyntaxTreesInParallel(
        Compilation compilation,
        Action<SyntaxTree, SemanticModel> processor)
    {
        var syntaxTrees = compilation.SyntaxTrees.ToList();
        
        System.Threading.Tasks.Parallel.ForEach(syntaxTrees, tree =>
        {
            var semanticModel = compilation.GetSemanticModel(tree);
            processor(tree, semanticModel);
        });
    }
    
    // 优化 5: 最小化引用集
    public IEnumerable<MetadataReference> GetMinimalReferences(
        string sourceCode)
    {
        var references = new List<MetadataReference>();
        
        // 始终需要的基础引用
        references.Add(GetOrCreateReference(
            typeof(object).Assembly.Location));
        
        // 根据代码内容动态添加引用
        if (sourceCode.Contains("Console"))
        {
            references.Add(GetOrCreateReference(
                typeof(Console).Assembly.Location));
        }
        
        if (sourceCode.Contains("Linq") || 
            sourceCode.Contains("Select") ||
            sourceCode.Contains("Where"))
        {
            references.Add(GetOrCreateReference(
                typeof(Enumerable).Assembly.Location));
        }
        
        if (sourceCode.Contains("Task") || sourceCode.Contains("async"))
        {
            references.Add(GetOrCreateReference(
                typeof(System.Threading.Tasks.Task).Assembly.Location));
        }
        
        return references;
    }
    
    // 性能测量
    public CompilationPerformanceMetrics MeasurePerformance(
        CSharpCompilation compilation)
    {
        var metrics = new CompilationPerformanceMetrics();
        var stopwatch = Stopwatch.StartNew();
        
        // 测量语义模型创建时间
        stopwatch.Restart();
        foreach (var tree in compilation.SyntaxTrees)
        {
            var model = compilation.GetSemanticModel(tree);
        }
        metrics.SemanticModelCreationTime = stopwatch.Elapsed;
        
        // 测量诊断分析时间
        stopwatch.Restart();
        var diagnostics = compilation.GetDiagnostics();
        metrics.DiagnosticAnalysisTime = stopwatch.Elapsed;
        
        // 测量 Emit 时间
        stopwatch.Restart();
        using var ms = new System.IO.MemoryStream();
        var result = compilation.Emit(ms);
        metrics.EmitTime = stopwatch.Elapsed;
        metrics.Success = result.Success;
        
        return metrics;
    }
    
    // 生成缓存键
    private string GenerateCacheKey(
        string assemblyName,
        IEnumerable<SyntaxTree> syntaxTrees)
    {
        var treeHashes = string.Join(",", 
            syntaxTrees.Select(t => t.GetRoot().GetHashCode()));
        return $"{assemblyName}_{treeHashes}";
    }
    
    // 清理缓存
    public void ClearCaches()
    {
        _compilationCache.Clear();
        _referenceCache.Clear();
        _semanticModelCache.Clear();
    }
}

public class CompilationPerformanceMetrics
{
    public TimeSpan SemanticModelCreationTime { get; set; }
    public TimeSpan DiagnosticAnalysisTime { get; set; }
    public TimeSpan EmitTime { get; set; }
    public bool Success { get; set; }
    
    public TimeSpan TotalTime => 
        SemanticModelCreationTime + 
        DiagnosticAnalysisTime + 
        EmitTime;
    
    public override string ToString()
    {
        return $@"
编译性能指标:
- 语义模型创建: {SemanticModelCreationTime.TotalMilliseconds:F2}ms
- 诊断分析: {DiagnosticAnalysisTime.TotalMilliseconds:F2}ms
- Emit: {EmitTime.TotalMilliseconds:F2}ms
- 总时间: {TotalTime.TotalMilliseconds:F2}ms
- 成功: {Success}";
    }
}

关键要点:

  • 缓存 Compilation、MetadataReference 和 SemanticModel
  • 使用并行处理加速多文件分析
  • 最小化引用集减少加载时间
  • 测量性能找出瓶颈

性能提升:

  • 缓存可提升 50-80% 的性能
  • 并行处理可提升 2-4 倍速度(取决于 CPU 核心数)
  • 最小化引用可减少 30-50% 的加载时间

💡 实际场景

场景 1:构建代码分析工具

问题: 需要构建一个分析整个解决方案的工具

解决方案:

csharp
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Generic;
using System.IO;
using System.Linq;

public class SolutionAnalyzer
{
    public AnalysisResult AnalyzeSolution(string solutionPath)
    {
        var result = new AnalysisResult();
        
        // 1. 加载解决方案中的所有项目
        var projects = LoadProjects(solutionPath);
        
        // 2. 为每个项目创建编译
        foreach (var project in projects)
        {
            var compilation = CreateCompilationForProject(project);
            
            // 3. 分析编译
            var projectAnalysis = AnalyzeCompilation(compilation);
            result.ProjectAnalyses.Add(project.Name, projectAnalysis);
        }
        
        // 4. 生成跨项目分析
        result.CrossProjectAnalysis = AnalyzeCrossProjectDependencies(projects);
        
        return result;
    }
    
    private List<ProjectInfo> LoadProjects(string solutionPath)
    {
        var projects = new List<ProjectInfo>();
        var solutionDir = Path.GetDirectoryName(solutionPath);
        
        // 查找所有 .csproj 文件
        var projectFiles = Directory.GetFiles(
            solutionDir, 
            "*.csproj", 
            SearchOption.AllDirectories);
        
        foreach (var projectFile in projectFiles)
        {
            projects.Add(new ProjectInfo
            {
                Name = Path.GetFileNameWithoutExtension(projectFile),
                Path = projectFile,
                SourceFiles = GetSourceFiles(Path.GetDirectoryName(projectFile))
            });
        }
        
        return projects;
    }
    
    private List<string> GetSourceFiles(string projectDir)
    {
        return Directory.GetFiles(projectDir, "*.cs", SearchOption.AllDirectories)
            .Where(f => !f.Contains("\\obj\\") && !f.Contains("\\bin\\"))
            .ToList();
    }
    
    private CSharpCompilation CreateCompilationForProject(ProjectInfo project)
    {
        // 解析所有源文件
        var syntaxTrees = project.SourceFiles
            .Select(file => CSharpSyntaxTree.ParseText(
                File.ReadAllText(file), 
                path: file))
            .ToList();
        
        // 添加标准引用
        var references = GetStandardReferences();
        
        // 创建编译
        return CSharpCompilation.Create(
            assemblyName: project.Name,
            syntaxTrees: syntaxTrees,
            references: references,
            options: new CSharpCompilationOptions(
                OutputKind.DynamicallyLinkedLibrary));
    }
    
    private ProjectAnalysis AnalyzeCompilation(CSharpCompilation compilation)
    {
        var analysis = new ProjectAnalysis();
        
        // 统计代码行数
        analysis.TotalLines = compilation.SyntaxTrees
            .Sum(t => t.GetRoot().GetText().Lines.Count);
        
        // 统计类数量
        analysis.ClassCount = compilation.SyntaxTrees
            .SelectMany(t => t.GetRoot().DescendantNodes())
            .OfType<ClassDeclarationSyntax>()
            .Count();
        
        // 统计方法数量
        analysis.MethodCount = compilation.SyntaxTrees
            .SelectMany(t => t.GetRoot().DescendantNodes())
            .OfType<MethodDeclarationSyntax>()
            .Count();
        
        // 分析诊断
        var diagnostics = compilation.GetDiagnostics();
        analysis.ErrorCount = diagnostics.Count(d => 
            d.Severity == DiagnosticSeverity.Error);
        analysis.WarningCount = diagnostics.Count(d => 
            d.Severity == DiagnosticSeverity.Warning);
        
        return analysis;
    }
    
    private CrossProjectAnalysis AnalyzeCrossProjectDependencies(
        List<ProjectInfo> projects)
    {
        // 分析项目间依赖关系
        return new CrossProjectAnalysis
        {
            TotalProjects = projects.Count,
            // 更多跨项目分析...
        };
    }
    
    private IEnumerable<MetadataReference> GetStandardReferences()
    {
        return new[]
        {
            MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
            MetadataReference.CreateFromFile(typeof(System.Console).Assembly.Location),
            MetadataReference.CreateFromFile(typeof(System.Linq.Enumerable).Assembly.Location)
        };
    }
}

public class ProjectInfo
{
    public string Name { get; set; }
    public string Path { get; set; }
    public List<string> SourceFiles { get; set; }
}

public class AnalysisResult
{
    public Dictionary<string, ProjectAnalysis> ProjectAnalyses { get; set; } = new();
    public CrossProjectAnalysis CrossProjectAnalysis { get; set; }
}

public class ProjectAnalysis
{
    public int TotalLines { get; set; }
    public int ClassCount { get; set; }
    public int MethodCount { get; set; }
    public int ErrorCount { get; set; }
    public int WarningCount { get; set; }
}

public class CrossProjectAnalysis
{
    public int TotalProjects { get; set; }
}

说明: 这个示例展示了如何构建一个完整的解决方案分析工具,包括加载项目、创建编译、分析代码和生成报告。

场景 2:实现编译缓存系统

问题: 需要为频繁编译的场景实现高效的缓存系统

解决方案:

csharp
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;

public class CompilationCacheSystem
{
    private readonly ConcurrentDictionary<string, CachedCompilation> _cache = new();
    private readonly TimeSpan _cacheExpiration = TimeSpan.FromMinutes(30);
    
    // 获取或创建编译(带缓存)
    public CSharpCompilation GetOrCreateCompilation(
        string assemblyName,
        IEnumerable<string> sourceFiles,
        IEnumerable<MetadataReference> references)
    {
        // 生成缓存键
        var cacheKey = GenerateCacheKey(assemblyName, sourceFiles);
        
        // 检查缓存
        if (_cache.TryGetValue(cacheKey, out var cached))
        {
            // 检查是否过期
            if (DateTime.UtcNow - cached.CreatedAt < _cacheExpiration)
            {
                return cached.Compilation;
            }
            
            // 过期,移除
            _cache.TryRemove(cacheKey, out _);
        }
        
        // 创建新编译
        var syntaxTrees = sourceFiles
            .Select(file => CSharpSyntaxTree.ParseText(
                System.IO.File.ReadAllText(file), 
                path: file))
            .ToList();
        
        var compilation = CSharpCompilation.Create(
            assemblyName: assemblyName,
            syntaxTrees: syntaxTrees,
            references: references,
            options: new CSharpCompilationOptions(
                OutputKind.DynamicallyLinkedLibrary));
        
        // 缓存
        _cache[cacheKey] = new CachedCompilation
        {
            Compilation = compilation,
            CreatedAt = DateTime.UtcNow
        };
        
        return compilation;
    }
    
    // 生成缓存键(基于内容哈希)
    private string GenerateCacheKey(
        string assemblyName,
        IEnumerable<string> sourceFiles)
    {
        var sb = new StringBuilder();
        sb.Append(assemblyName);
        
        foreach (var file in sourceFiles.OrderBy(f => f))
        {
            var content = System.IO.File.ReadAllText(file);
            var hash = ComputeHash(content);
            sb.Append(hash);
        }
        
        return ComputeHash(sb.ToString());
    }
    
    // 计算内容哈希
    private string ComputeHash(string content)
    {
        using var sha256 = SHA256.Create();
        var bytes = Encoding.UTF8.GetBytes(content);
        var hash = sha256.ComputeHash(bytes);
        return Convert.ToBase64String(hash);
    }
    
    // 清理过期缓存
    public void CleanupExpiredCache()
    {
        var now = DateTime.UtcNow;
        var expiredKeys = _cache
            .Where(kvp => now - kvp.Value.CreatedAt >= _cacheExpiration)
            .Select(kvp => kvp.Key)
            .ToList();
        
        foreach (var key in expiredKeys)
        {
            _cache.TryRemove(key, out _);
        }
    }
    
    // 获取缓存统计
    public CacheStatistics GetStatistics()
    {
        return new CacheStatistics
        {
            TotalCachedCompilations = _cache.Count,
            OldestCacheAge = _cache.Values
                .Select(c => DateTime.UtcNow - c.CreatedAt)
                .DefaultIfEmpty()
                .Max()
        };
    }
    
    // 清空所有缓存
    public void ClearAll()
    {
        _cache.Clear();
    }
}

public class CachedCompilation
{
    public CSharpCompilation Compilation { get; set; }
    public DateTime CreatedAt { get; set; }
}

public class CacheStatistics
{
    public int TotalCachedCompilations { get; set; }
    public TimeSpan OldestCacheAge { get; set; }
}

说明: 实现了一个完整的编译缓存系统,包括基于内容哈希的缓存键生成、过期管理和统计功能。

🚀 性能优化技巧

技巧 1:使用 Compilation.WithXxx() 方法

问题: 频繁创建新的 Compilation 对象影响性能

解决方案:

csharp
// 差:每次都创建新编译
var compilation1 = CSharpCompilation.Create("Assembly1", ...);
var compilation2 = CSharpCompilation.Create("Assembly2", ...);

// 好:重用基础编译
var baseCompilation = CSharpCompilation.Create("Base", ...);
var compilation1 = baseCompilation.WithAssemblyName("Assembly1");
var compilation2 = baseCompilation.WithAssemblyName("Assembly2");

技巧 2:延迟获取语义模型

问题: 提前创建所有语义模型浪费资源

解决方案:

csharp
// 差:提前创建所有语义模型
var models = compilation.SyntaxTrees
    .Select(t => compilation.GetSemanticModel(t))
    .ToList();

// 好:按需创建
foreach (var tree in compilation.SyntaxTrees)
{
    if (NeedsSemanticAnalysis(tree))
    {
        var model = compilation.GetSemanticModel(tree);
        // 使用 model...
    }
}

技巧 3:批量操作语法树

问题: 多次单独添加/删除语法树效率低

解决方案:

csharp
// 差:多次单独操作
foreach (var tree in treesToAdd)
{
    compilation = compilation.AddSyntaxTrees(tree);
}

// 好:批量操作
compilation = compilation.AddSyntaxTrees(treesToAdd);

⚠️ 常见陷阱

陷阱 1:忘记处理循环引用

问题: 跨程序集查找时可能遇到循环引用

错误示例:

csharp
// 可能导致无限递归
private INamedTypeSymbol FindType(INamespaceSymbol ns, string name)
{
    foreach (var child in ns.GetNamespaceMembers())
    {
        return FindType(child, name); // 没有终止条件
    }
    return null;
}

正确方法:

csharp
private INamedTypeSymbol FindType(
    INamespaceSymbol ns, 
    string name,
    HashSet<INamespaceSymbol> visited = null)
{
    visited ??= new HashSet<INamespaceSymbol>(SymbolEqualityComparer.Default);
    
    if (!visited.Add(ns))
    {
        return null; // 已访问过,避免循环
    }
    
    var type = ns.GetTypeMembers(name).FirstOrDefault();
    if (type != null) return type;
    
    foreach (var child in ns.GetNamespaceMembers())
    {
        type = FindType(child, name, visited);
        if (type != null) return type;
    }
    
    return null;
}

陷阱 2:缓存过期的编译对象

问题: 使用过期的编译对象导致错误结果

错误示例:

csharp
// 缓存编译但不检查源文件是否更改
private static CSharpCompilation _cachedCompilation;

public CSharpCompilation GetCompilation(string sourceFile)
{
    if (_cachedCompilation != null)
    {
        return _cachedCompilation; // 可能已过期
    }
    // ...
}

正确方法:

csharp
private static CSharpCompilation _cachedCompilation;
private static DateTime _cacheTime;
private static string _cachedFilePath;

public CSharpCompilation GetCompilation(string sourceFile)
{
    var fileModified = File.GetLastWriteTimeUtc(sourceFile);
    
    if (_cachedCompilation != null && 
        _cachedFilePath == sourceFile &&
        _cacheTime >= fileModified)
    {
        return _cachedCompilation;
    }
    
    // 重新创建编译
    _cachedCompilation = CreateCompilation(sourceFile);
    _cacheTime = DateTime.UtcNow;
    _cachedFilePath = sourceFile;
    
    return _cachedCompilation;
}

陷阱 3:不处理大型程序集的内存问题

问题: 加载大型程序集可能导致内存溢出

错误示例:

csharp
// 一次性加载所有程序集
var references = Directory.GetFiles(runtimePath, "*.dll")
    .Select(f => MetadataReference.CreateFromFile(f))
    .ToList();

正确方法:

csharp
// 按需加载,使用弱引用
private readonly Dictionary<string, WeakReference<MetadataReference>> 
    _referenceCache = new();

public MetadataReference GetReference(string path)
{
    if (_referenceCache.TryGetValue(path, out var weakRef) &&
        weakRef.TryGetTarget(out var reference))
    {
        return reference;
    }
    
    reference = MetadataReference.CreateFromFile(path);
    _referenceCache[path] = new WeakReference<MetadataReference>(reference);
    
    return reference;
}

🔍 深入探讨

主题 1:编译的内部工作原理

Compilation 对象在内部维护了复杂的数据结构:

符号表: 存储所有类型、方法、属性等符号 绑定器: 负责名称解析和类型检查 诊断引擎: 收集和报告编译错误

示例:

csharp
public class CompilationInternals
{
    public void ExploreInternals(CSharpCompilation compilation)
    {
        // 1. 符号表
        var assembly = compilation.Assembly;
        var globalNamespace = assembly.GlobalNamespace;
        
        // 2. 类型系统
        var objectType = compilation.GetSpecialType(SpecialType.System_Object);
        var stringType = compilation.GetSpecialType(SpecialType.System_String);
        
        // 3. 诊断系统
        var diagnostics = compilation.GetDiagnostics();
        
        // 4. 元数据
        var references = compilation.References;
        var externalReferences = compilation.ExternalReferences;
    }
}

主题 2:编译选项的深层影响

不同的编译选项会影响生成的代码:

OptimizationLevel.Debug:

  • 保留所有局部变量
  • 不内联方法
  • 生成序列点用于调试

OptimizationLevel.Release:

  • 移除未使用的代码
  • 内联小方法
  • 优化循环

示例:

csharp
public class OptimizationImpact
{
    public void CompareOptimizations()
    {
        var sourceCode = @"
public class Test
{
    public int Add(int a, int b) => a + b;
    
    public void UseAdd()
    {
        var result = Add(1, 2);
    }
}";
        
        // Debug 编译
        var debugCompilation = CreateCompilation(
            sourceCode, 
            OptimizationLevel.Debug);
        
        // Release 编译
        var releaseCompilation = CreateCompilation(
            sourceCode, 
            OptimizationLevel.Release);
        
        // Release 版本可能会内联 Add 方法
    }
    
    private CSharpCompilation CreateCompilation(
        string code, 
        OptimizationLevel level)
    {
        var tree = CSharpSyntaxTree.ParseText(code);
        return CSharpCompilation.Create(
            "Test",
            new[] { tree },
            options: new CSharpCompilationOptions(
                OutputKind.DynamicallyLinkedLibrary,
                optimizationLevel: level));
    }
}

⏭️ 下一步

🔗 相关资源

📚 在学习路径中使用

此 API 在以下步骤中使用:

💬 另请参阅

📝 总结

编译 API 的高级用法包括:

  1. 跨程序集符号查找 - 在多个程序集中查找和解析类型
  2. 增量编译 - 高效更新部分代码而不重建整个编译
  3. 性能优化 - 通过缓存、并行处理和最小化引用提升性能
  4. 复杂场景处理 - 构建代码分析工具、实现编译缓存系统

掌握这些高级技术后,你将能够:

  • 构建高性能的代码分析工具
  • 实现复杂的代码生成场景
  • 优化大型项目的编译性能
  • 处理企业级的编译需求

继续探索其他高级主题,深入理解 Roslyn 的强大功能!

基于 MIT 许可发布