诊断 API 高级指南
🎯 学习目标
完成本指南后,你将能够:
- [ ] 实现高性能的诊断分析器
- [ ] 处理复杂的分析场景
- [ ] 优化分析器性能
- [ ] 实现编译级别的分析
- [ ] 处理增量分析
📖 核心概念
性能优化的重要性
诊断分析器在 IDE 中频繁运行,性能至关重要:
- 影响用户体验
- 影响编译速度
- 影响 IDE 响应性
高级分析技术
- 编译分析 - 分析整个编译单元
- 增量分析 - 只分析变化的部分
- 缓存策略 - 缓存分析结果
- 并发执行 - 利用多核处理器
⚡ 性能优化
1. 启用并发执行
csharp
public override void Initialize(AnalysisContext context)
{
// 启用并发执行 - 必须调用!
context.EnableConcurrentExecution();
// 配置生成代码分析
context.ConfigureGeneratedCodeAnalysis(
GeneratedCodeAnalysisFlags.None);
context.RegisterSyntaxNodeAction(Analyze, SyntaxKind.ClassDeclaration);
}2. 快速检查优先
csharp
private void Analyze(SyntaxNodeAnalysisContext context)
{
var node = context.Node;
// ✅ 先进行快速的语法检查
if (!QuickSyntaxCheck(node))
return;
// 只在必要时进行语义分析
var semanticModel = context.SemanticModel;
var typeInfo = semanticModel.GetTypeInfo(node, context.CancellationToken);
if (DetailedCheck(typeInfo))
{
ReportDiagnostic(context, node);
}
}
private bool QuickSyntaxCheck(SyntaxNode node)
{
// 快速的语法检查,不需要语义信息
return node.ChildNodes().Count() > 0;
}3. 缓存重复查询
csharp
public class CachedAnalyzer : DiagnosticAnalyzer
{
// 使用 ConditionalWeakTable 缓存结果
private static readonly ConditionalWeakTable<Compilation, ConcurrentDictionary<ISymbol, bool>>
_cache = new();
private void AnalyzeSymbol(SymbolAnalysisContext context)
{
var compilation = context.Compilation;
var symbol = context.Symbol;
// 获取或创建缓存
var symbolCache = _cache.GetOrCreateValue(compilation);
// 检查缓存
if (symbolCache.TryGetValue(symbol, out var cachedResult))
{
if (cachedResult)
{
ReportDiagnostic(context, symbol);
}
return;
}
// 执行分析
bool hasIssue = PerformAnalysis(symbol);
// 缓存结果
symbolCache.TryAdd(symbol, hasIssue);
if (hasIssue)
{
ReportDiagnostic(context, symbol);
}
}
private bool PerformAnalysis(ISymbol symbol)
{
// 执行实际的分析逻辑
return false;
}
private void ReportDiagnostic(SymbolAnalysisContext context, ISymbol symbol) { }
}4. 避免重复的语义查询
csharp
private void AnalyzeMethod(SyntaxNodeAnalysisContext context)
{
var method = (MethodDeclarationSyntax)context.Node;
// ❌ 不好 - 多次查询语义模型
foreach (var statement in method.Body.Statements)
{
var typeInfo = context.SemanticModel.GetTypeInfo(statement);
// ...
}
// ✅ 好 - 批量收集后一次性查询
var statements = method.Body.Statements.ToList();
var typeInfos = statements
.Select(s => context.SemanticModel.GetTypeInfo(s, context.CancellationToken))
.ToList();
for (int i = 0; i < statements.Count; i++)
{
var statement = statements[i];
var typeInfo = typeInfos[i];
// ...
}
}🔄 编译级别分析
RegisterCompilationAction
编译级别分析可以访问整个编译单元的信息。
csharp
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class AssemblyLevelAnalyzer : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(
id: "ASSEMBLY001",
title: "程序集级别检查",
messageFormat: "程序集包含 {0} 个公共类型",
category: "Info",
defaultSeverity: DiagnosticSeverity.Info,
isEnabledByDefault: true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
ImmutableArray.Create(Rule);
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
// 注册编译开始和结束动作
context.RegisterCompilationStartAction(OnCompilationStart);
}
private void OnCompilationStart(CompilationStartAnalysisContext context)
{
// 创建编译级别的状态
var analyzer = new CompilationAnalyzer();
// 注册符号动作
context.RegisterSymbolAction(
analyzer.AnalyzeSymbol,
SymbolKind.NamedType);
// 注册编译结束动作
context.RegisterCompilationEndAction(analyzer.OnCompilationEnd);
}
private class CompilationAnalyzer
{
private int _publicTypeCount = 0;
public void AnalyzeSymbol(SymbolAnalysisContext context)
{
var typeSymbol = (INamedTypeSymbol)context.Symbol;
if (typeSymbol.DeclaredAccessibility == Accessibility.Public)
{
Interlocked.Increment(ref _publicTypeCount);
}
}
public void OnCompilationEnd(CompilationAnalysisContext context)
{
// 在编译结束时报告统计信息
if (_publicTypeCount > 0)
{
var diagnostic = Diagnostic.Create(
Rule,
Location.None,
_publicTypeCount);
context.ReportDiagnostic(diagnostic);
}
}
}
}跨文件分析
csharp
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class CrossFileAnalyzer : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(
id: "CROSS001",
title: "跨文件依赖检查",
messageFormat: "类型 '{0}' 依赖于 '{1}'",
category: "Design",
defaultSeverity: DiagnosticSeverity.Info,
isEnabledByDefault: true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
ImmutableArray.Create(Rule);
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterCompilationStartAction(OnCompilationStart);
}
private void OnCompilationStart(CompilationStartAnalysisContext context)
{
var analyzer = new DependencyAnalyzer();
// 收集所有类型
context.RegisterSymbolAction(
analyzer.CollectType,
SymbolKind.NamedType);
// 分析依赖关系
context.RegisterCompilationEndAction(analyzer.AnalyzeDependencies);
}
private class DependencyAnalyzer
{
private readonly ConcurrentBag<INamedTypeSymbol> _types = new();
public void CollectType(SymbolAnalysisContext context)
{
var typeSymbol = (INamedTypeSymbol)context.Symbol;
_types.Add(typeSymbol);
}
public void AnalyzeDependencies(CompilationAnalysisContext context)
{
foreach (var type in _types)
{
// 分析类型的依赖
var dependencies = GetDependencies(type);
foreach (var dependency in dependencies)
{
var location = type.Locations.FirstOrDefault();
if (location != null && !location.IsInMetadata)
{
var diagnostic = Diagnostic.Create(
Rule,
location,
type.Name,
dependency.Name);
context.ReportDiagnostic(diagnostic);
}
}
}
}
private IEnumerable<INamedTypeSymbol> GetDependencies(INamedTypeSymbol type)
{
var dependencies = new HashSet<INamedTypeSymbol>(SymbolEqualityComparer.Default);
// 基类
if (type.BaseType != null &&
type.BaseType.SpecialType == SpecialType.None)
{
dependencies.Add(type.BaseType);
}
// 接口
foreach (var @interface in type.Interfaces)
{
dependencies.Add(@interface);
}
// 字段和属性类型
foreach (var member in type.GetMembers())
{
if (member is IFieldSymbol field)
{
if (field.Type is INamedTypeSymbol fieldType)
dependencies.Add(fieldType);
}
else if (member is IPropertySymbol property)
{
if (property.Type is INamedTypeSymbol propertyType)
dependencies.Add(propertyType);
}
}
return dependencies;
}
}
}🎯 复杂分析场景
数据流分析
csharp
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class NullReferenceAnalyzer : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(
id: "NULL001",
title: "可能的空引用",
messageFormat: "变量 '{0}' 可能为 null",
category: "Safety",
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
ImmutableArray.Create(Rule);
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterSyntaxNodeAction(
AnalyzeMethod,
SyntaxKind.MethodDeclaration);
}
private void AnalyzeMethod(SyntaxNodeAnalysisContext context)
{
var method = (MethodDeclarationSyntax)context.Node;
if (method.Body == null)
return;
// 跟踪可能为 null 的变量
var nullableVariables = new HashSet<string>();
foreach (var statement in method.Body.Statements)
{
AnalyzeStatement(
context,
statement,
nullableVariables);
}
}
private void AnalyzeStatement(
SyntaxNodeAnalysisContext context,
StatementSyntax statement,
HashSet<string> nullableVariables)
{
// 变量声明
if (statement is LocalDeclarationStatementSyntax localDecl)
{
foreach (var variable in localDecl.Declaration.Variables)
{
if (variable.Initializer == null)
{
// 未初始化的变量可能为 null
nullableVariables.Add(variable.Identifier.Text);
}
}
}
// 赋值语句
else if (statement is ExpressionStatementSyntax exprStatement)
{
if (exprStatement.Expression is AssignmentExpressionSyntax assignment)
{
if (assignment.Left is IdentifierNameSyntax identifier)
{
var rightType = context.SemanticModel.GetTypeInfo(
assignment.Right,
context.CancellationToken);
if (rightType.Type != null && !rightType.Type.IsValueType)
{
// 引用类型赋值,可能为 null
nullableVariables.Add(identifier.Identifier.Text);
}
}
}
}
// 成员访问
else if (statement.DescendantNodes().OfType<MemberAccessExpressionSyntax>().Any())
{
foreach (var memberAccess in statement.DescendantNodes()
.OfType<MemberAccessExpressionSyntax>())
{
if (memberAccess.Expression is IdentifierNameSyntax identifier)
{
if (nullableVariables.Contains(identifier.Identifier.Text))
{
// 报告可能的空引用
var diagnostic = Diagnostic.Create(
Rule,
identifier.GetLocation(),
identifier.Identifier.Text);
context.ReportDiagnostic(diagnostic);
}
}
}
}
}
}控制流分析
csharp
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class UnreachableCodeAnalyzer : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(
id: "FLOW001",
title: "不可达代码",
messageFormat: "此代码永远不会执行",
category: "Usage",
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
ImmutableArray.Create(Rule);
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterSyntaxNodeAction(
AnalyzeMethod,
SyntaxKind.MethodDeclaration);
}
private void AnalyzeMethod(SyntaxNodeAnalysisContext context)
{
var method = (MethodDeclarationSyntax)context.Node;
if (method.Body == null)
return;
bool hasReturn = false;
foreach (var statement in method.Body.Statements)
{
if (hasReturn)
{
// return 之后的代码不可达
var diagnostic = Diagnostic.Create(
Rule,
statement.GetLocation());
context.ReportDiagnostic(diagnostic);
}
if (statement is ReturnStatementSyntax)
{
hasReturn = true;
}
}
}
}🧪 测试和调试
单元测试
csharp
using Microsoft.CodeAnalysis.Testing;
using Xunit;
public class DiagnosticAnalyzerTests
{
[Fact]
public async Task TestClassNameAnalyzer()
{
var test = @"
class myClass { } // 应该报告诊断
";
var expected = DiagnosticResult
.CompilerWarning("NAMING001")
.WithSpan(2, 19, 2, 26)
.WithArguments("myClass");
await VerifyCS.VerifyAnalyzerAsync(test, expected);
}
[Fact]
public async Task TestNoWarningForCorrectName()
{
var test = @"
class MyClass { } // 不应该报告诊断
";
await VerifyCS.VerifyAnalyzerAsync(test);
}
}性能测试
csharp
using BenchmarkDotNet.Attributes;
[MemoryDiagnoser]
public class AnalyzerBenchmarks
{
private Compilation _compilation;
private DiagnosticAnalyzer _analyzer;
[GlobalSetup]
public void Setup()
{
// 创建测试编译
var code = GenerateTestCode(1000); // 1000 个类
_compilation = CreateCompilation(code);
_analyzer = new MyAnalyzer();
}
[Benchmark]
public void RunAnalyzer()
{
var compilationWithAnalyzers = _compilation
.WithAnalyzers(ImmutableArray.Create(_analyzer));
var diagnostics = compilationWithAnalyzers
.GetAllDiagnosticsAsync()
.Result;
}
private string GenerateTestCode(int classCount)
{
var sb = new StringBuilder();
for (int i = 0; i < classCount; i++)
{
sb.AppendLine($"class Class{i} {{ }}");
}
return sb.ToString();
}
private Compilation CreateCompilation(string code)
{
var syntaxTree = CSharpSyntaxTree.ParseText(code);
return CSharpCompilation.Create(
"TestAssembly",
new[] { syntaxTree },
new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) });
}
}💡 高级技巧
1. 使用 SymbolEqualityComparer
csharp
// ✅ 正确 - 使用 SymbolEqualityComparer
var set = new HashSet<ISymbol>(SymbolEqualityComparer.Default);
// ❌ 错误 - 使用默认比较器
var set = new HashSet<ISymbol>();2. 处理 Partial 类
csharp
private void AnalyzeType(SymbolAnalysisContext context)
{
var typeSymbol = (INamedTypeSymbol)context.Symbol;
// Partial 类可能有多个位置
foreach (var location in typeSymbol.Locations)
{
if (!location.IsInMetadata)
{
// 处理每个位置
}
}
}3. 使用取消令牌
csharp
private void Analyze(SyntaxNodeAnalysisContext context)
{
// 定期检查取消
context.CancellationToken.ThrowIfCancellationRequested();
// 在长时间运行的操作中使用
var typeInfo = context.SemanticModel.GetTypeInfo(
node,
context.CancellationToken);
}✅ 最佳实践总结
性能优先
- 启用并发执行
- 快速检查优先
- 缓存重复查询
正确使用分析动作
- 语法分析用于简单检查
- 符号分析用于类型检查
- 编译分析用于全局检查
测试覆盖
- 编写单元测试
- 进行性能测试
- 测试边界情况
用户体验
- 提供清晰的消息
- 使用精确的位置
- 提供附加位置
🔗 相关资源
基础回顾
完整参考
最后更新: 2025-01-21