调试管道
掌握增量生成器管道的调试技巧和诊断方法
📋 文档信息
| 属性 | 值 |
|---|---|
| 难度 | 中级 |
| 阅读时间 | 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();
}
});
}🔑 关键要点
调试最佳实践
使用 WithTrackingName
- 为每个管道步骤添加跟踪名称
- 便于性能分析和问题定位
使用 Debug.WriteLine
- 记录关键步骤的执行
- 使用结构化日志格式
设置条件断点
- 只在特定条件下中断
- 避免频繁中断影响调试
测量性能
- 使用 Stopwatch 测量耗时
- 识别性能瓶颈
验证缓存
- 检查 IEquatable 实现
- 验证 GetHashCode 一致性
🔗 相关资源
🚀 下一步
最后更新: 2026-02-05