Skip to content

调试管道

掌握增量生成器管道的调试技巧和诊断方法

📋 文档信息

属性
难度中级
阅读时间20 分钟
前置知识管道操作基础
相关文档性能优化

🎯 学习目标

完成本文档后,你将能够:

  • ✅ 使用 WithTrackingName 跟踪管道
  • ✅ 使用 Debug.WriteLine 记录日志
  • ✅ 设置断点调试管道
  • ✅ 诊断常见的管道问题
  • ✅ 使用性能分析工具

📚 快速导航

章节说明
WithTrackingName添加跟踪名称
日志记录使用 Debug.WriteLine
断点调试设置断点
常见问题诊断常见问题

WithTrackingName

基本用法

csharp
using Microsoft.CodeAnalysis;

/// <summary>
/// 使用 WithTrackingName 跟踪管道
/// </summary>
public class TrackingNameDemo
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        var classes = context.SyntaxProvider
            .CreateSyntaxProvider(
                predicate: (node, _) => node is ClassDeclarationSyntax,
                transform: (ctx, _) => (ClassDeclarationSyntax)ctx.Node)
            .WithTrackingName("FindClasses");  // 添加跟踪名称
        
        var publicClasses = classes
            .Where((classDecl, _) =>
                classDecl.Modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword)))
            .WithTrackingName("FilterPublicClasses");  // 添加跟踪名称
        
        var classNames = publicClasses
            .Select((classDecl, _) => classDecl.Identifier.Text)
            .WithTrackingName("ExtractClassNames");  // 添加跟踪名称
        
        context.RegisterSourceOutput(classNames, (spc, name) =>
        {
            var code = $"// Class: {name}";
            spc.AddSource($"{name}.g.cs", code);
        });
    }
}

跟踪名称的好处


日志记录

使用 Debug.WriteLine

csharp
using Microsoft.CodeAnalysis;
using System.Diagnostics;

/// <summary>
/// 使用 Debug.WriteLine 记录日志
/// </summary>
public class LoggingDemo
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        var classes = context.SyntaxProvider
            .CreateSyntaxProvider(
                predicate: (node, _) =>
                {
                    var isClass = node is ClassDeclarationSyntax;
                    Debug.WriteLine($"Predicate: {node.Kind()} -> {isClass}");
                    return isClass;
                },
                transform: (ctx, _) =>
                {
                    var classDecl = (ClassDeclarationSyntax)ctx.Node;
                    var className = classDecl.Identifier.Text;
                    Debug.WriteLine($"Transform: {className}");
                    return className;
                });
        
        var filtered = classes
            .Where((name, _) =>
            {
                var shouldInclude = name.StartsWith("My");
                Debug.WriteLine($"Filter: {name} -> {shouldInclude}");
                return shouldInclude;
            });
        
        context.RegisterSourceOutput(filtered, (spc, name) =>
        {
            Debug.WriteLine($"Generate: {name}");
            var code = $"// Class: {name}";
            spc.AddSource($"{name}.g.cs", code);
        });
    }
}

结构化日志

csharp
using System.Diagnostics;

/// <summary>
/// 结构化日志示例
/// </summary>
public class StructuredLoggingDemo
{
    private static void Log(string stage, string message, object data = null)
    {
        var timestamp = DateTime.Now.ToString("HH:mm:ss.fff");
        var dataStr = data != null ? $" | Data: {data}" : "";
        Debug.WriteLine($"[{timestamp}] [{stage}] {message}{dataStr}");
    }
    
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        var classes = context.SyntaxProvider
            .CreateSyntaxProvider(
                predicate: (node, _) =>
                {
                    var isClass = node is ClassDeclarationSyntax;
                    Log("Predicate", $"Checking {node.Kind()}", new { IsClass = isClass });
                    return isClass;
                },
                transform: (ctx, _) =>
                {
                    var classDecl = (ClassDeclarationSyntax)ctx.Node;
                    var className = classDecl.Identifier.Text;
                    Log("Transform", $"Processing class", new { ClassName = className });
                    return className;
                });
        
        context.RegisterSourceOutput(classes, (spc, name) =>
        {
            Log("Generate", $"Generating code", new { ClassName = name });
            var code = $"// Class: {name}";
            spc.AddSource($"{name}.g.cs", code);
        });
    }
}

断点调试

设置断点

csharp
using Microsoft.CodeAnalysis;
using System.Diagnostics;

/// <summary>
/// 设置断点调试
/// </summary>
public class BreakpointDebuggingDemo
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        var classes = context.SyntaxProvider
            .CreateSyntaxProvider(
                predicate: (node, _) => node is ClassDeclarationSyntax,
                transform: (ctx, _) =>
                {
                    #if DEBUG
                    // 在这里设置断点
                    Debugger.Break();
                    #endif
                    
                    var classDecl = (ClassDeclarationSyntax)ctx.Node;
                    return classDecl.Identifier.Text;
                });
        
        context.RegisterSourceOutput(classes, (spc, name) =>
        {
            #if DEBUG
            // 只在特定条件下中断
            if (name == "MySpecialClass")
            {
                Debugger.Break();
            }
            #endif
            
            var code = $"// Class: {name}";
            spc.AddSource($"{name}.g.cs", code);
        });
    }
}

条件断点

csharp
using Microsoft.CodeAnalysis;
using System.Diagnostics;

/// <summary>
/// 条件断点示例
/// </summary>
public class ConditionalBreakpointDemo
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        var classes = context.SyntaxProvider
            .CreateSyntaxProvider(
                predicate: (node, _) => node is ClassDeclarationSyntax,
                transform: (ctx, _) =>
                {
                    var classDecl = (ClassDeclarationSyntax)ctx.Node;
                    var className = classDecl.Identifier.Text;
                    
                    #if DEBUG
                    // 只在特定类名时中断
                    if (className.Contains("Problem"))
                    {
                        Debugger.Break();
                    }
                    #endif
                    
                    return className;
                });
    }
}

常见问题

问题 1: 管道没有执行

症状: RegisterSourceOutput 没有被调用。

诊断:

csharp
public void Initialize(IncrementalGeneratorInitializationContext context)
{
    var classes = context.SyntaxProvider
        .CreateSyntaxProvider(
            predicate: (node, _) =>
            {
                Debug.WriteLine($"Predicate called for: {node.Kind()}");
                return node is ClassDeclarationSyntax;
            },
            transform: (ctx, _) =>
            {
                Debug.WriteLine("Transform called");
                return (ClassDeclarationSyntax)ctx.Node;
            });
    
    context.RegisterSourceOutput(classes, (spc, classDecl) =>
    {
        Debug.WriteLine($"RegisterSourceOutput called for: {classDecl.Identifier.Text}");
        // ...
    });
}

可能原因:

  • predicate 返回 false
  • transform 返回 null
  • Where 过滤掉了所有数据

问题 2: 缓存不生效

症状: 每次都重新生成代码。

诊断:

csharp
public record ClassInfo(string Name, ImmutableArray<string> Properties)
{
    // 添加调试方法
    public void DebugEquality(ClassInfo other)
    {
        Debug.WriteLine($"Comparing {Name} with {other.Name}");
        Debug.WriteLine($"  Name equal: {Name == other.Name}");
        Debug.WriteLine($"  Properties equal: {Properties.SequenceEqual(other.Properties)}");
        Debug.WriteLine($"  Result: {Equals(other)}");
    }
}

可能原因:

  • 没有实现 IEquatable
  • 使用了可变集合(List)
  • GetHashCode 不一致

问题 3: 性能问题

症状: 生成器运行缓慢。

诊断:

csharp
using System.Diagnostics;

public void Initialize(IncrementalGeneratorInitializationContext context)
{
    var classes = context.SyntaxProvider
        .CreateSyntaxProvider(
            predicate: (node, _) => node is ClassDeclarationSyntax,
            transform: (ctx, _) =>
            {
                var sw = Stopwatch.StartNew();
                
                var classDecl = (ClassDeclarationSyntax)ctx.Node;
                var symbol = ctx.SemanticModel.GetDeclaredSymbol(classDecl);
                var result = new ClassInfo(symbol.Name);
                
                sw.Stop();
                
                if (sw.ElapsedMilliseconds > 10)
                {
                    Debug.WriteLine($"Slow transform: {symbol.Name} took {sw.ElapsedMilliseconds}ms");
                }
                
                return result;
            })
        .WithTrackingName("TransformClasses");
}

可能原因:

  • 在 transform 中执行昂贵操作
  • 没有使用 ForAttributeWithMetadataName
  • 没有提前过滤

调试工具

性能分析器

csharp
using System.Collections.Concurrent;
using System.Diagnostics;

/// <summary>
/// 简单的性能分析器
/// </summary>
public class SimpleProfiler
{
    private static readonly ConcurrentDictionary<string, PerformanceStats> _stats = 
        new ConcurrentDictionary<string, PerformanceStats>();
    
    public static void Record(string operation, long milliseconds)
    {
        _stats.AddOrUpdate(
            operation,
            _ => new PerformanceStats { Count = 1, TotalMs = milliseconds },
            (_, stats) =>
            {
                stats.Count++;
                stats.TotalMs += milliseconds;
                if (milliseconds > stats.MaxMs)
                    stats.MaxMs = milliseconds;
                if (milliseconds < stats.MinMs || stats.MinMs == 0)
                    stats.MinMs = milliseconds;
                return stats;
            });
    }
    
    public static void PrintStatistics()
    {
        Debug.WriteLine("=== Performance Statistics ===");
        
        foreach (var kvp in _stats)
        {
            var operation = kvp.Key;
            var stats = kvp.Value;
            var avgMs = stats.TotalMs / stats.Count;
            
            Debug.WriteLine($"{operation}:");
            Debug.WriteLine($"  Count: {stats.Count}");
            Debug.WriteLine($"  Total: {stats.TotalMs}ms");
            Debug.WriteLine($"  Average: {avgMs}ms");
            Debug.WriteLine($"  Min: {stats.MinMs}ms");
            Debug.WriteLine($"  Max: {stats.MaxMs}ms");
        }
        
        Debug.WriteLine("==============================");
    }
    
    private class PerformanceStats
    {
        public long Count { get; set; }
        public long TotalMs { get; set; }
        public long MinMs { get; set; }
        public long MaxMs { get; set; }
    }
}

// 使用示例
public void Initialize(IncrementalGeneratorInitializationContext context)
{
    var classes = context.SyntaxProvider
        .CreateSyntaxProvider(
            predicate: (node, _) => node is ClassDeclarationSyntax,
            transform: (ctx, _) =>
            {
                var sw = Stopwatch.StartNew();
                
                var classDecl = (ClassDeclarationSyntax)ctx.Node;
                var symbol = ctx.SemanticModel.GetDeclaredSymbol(classDecl);
                var result = new ClassInfo(symbol.Name);
                
                sw.Stop();
                SimpleProfiler.Record("Transform", sw.ElapsedMilliseconds);
                
                return result;
            });
    
    context.RegisterSourceOutput(classes, (spc, info) =>
    {
        var sw = Stopwatch.StartNew();
        
        var code = GenerateCode(info);
        spc.AddSource($"{info.Name}.g.cs", code);
        
        sw.Stop();
        SimpleProfiler.Record("Generate", sw.ElapsedMilliseconds);
        
        // 每 100 个类打印一次统计
        if (SimpleProfiler._stats["Generate"].Count % 100 == 0)
        {
            SimpleProfiler.PrintStatistics();
        }
    });
}

🔑 关键要点

调试最佳实践

  1. 使用 WithTrackingName

    • 为每个管道步骤添加跟踪名称
    • 便于性能分析和问题定位
  2. 使用 Debug.WriteLine

    • 记录关键步骤的执行
    • 使用结构化日志格式
  3. 设置条件断点

    • 只在特定条件下中断
    • 避免频繁中断影响调试
  4. 测量性能

    • 使用 Stopwatch 测量耗时
    • 识别性能瓶颈
  5. 验证缓存

    • 检查 IEquatable 实现
    • 验证 GetHashCode 一致性

🔗 相关资源


🚀 下一步


最后更新: 2026-02-05

基于 MIT 许可发布