DiagnosticAnalyzer 实现
本文档详细介绍如何实现完整的诊断分析器。
📚 文档信息
- 难度级别: 高级
- 预计阅读时间: 30 分钟
🎯 学习目标
- ✅ 理解 DiagnosticAnalyzer 的基本结构
- ✅ 学会注册不同类型的分析动作
- ✅ 实现完整的诊断分析器
- ✅ 优化分析器性能
基本结构
csharp
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using System.Collections.Immutable;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class BasicAnalyzer : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(
id: "BASIC001",
title: "示例诊断",
messageFormat: "发现问题:'{0}'",
category: "Demo",
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(AnalyzeNode, SyntaxKind.ClassDeclaration);
}
private void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
var node = context.Node;
if (HasProblem(node))
{
var diagnostic = Diagnostic.Create(
Rule,
node.GetLocation(),
"问题描述");
context.ReportDiagnostic(diagnostic);
}
}
private bool HasProblem(SyntaxNode node)
{
return false;
}
}注册分析动作
RegisterSyntaxNodeAction - 语法节点分析
csharp
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxNodeAction(
AnalyzeClassDeclaration,
SyntaxKind.ClassDeclaration);
context.RegisterSyntaxNodeAction(
AnalyzeMethodDeclaration,
SyntaxKind.MethodDeclaration);
}RegisterSymbolAction - 符号分析
csharp
public override void Initialize(AnalysisContext context)
{
context.RegisterSymbolAction(
AnalyzeNamedType,
SymbolKind.NamedType);
context.RegisterSymbolAction(
AnalyzeMethod,
SymbolKind.Method);
}最佳实践
- 启用并发执行:context.EnableConcurrentExecution()
- 配置生成代码分析:context.ConfigureGeneratedCodeAnalysis()
- 使用合适的分析动作:选择最合适的分析阶段
- 避免耗时操作:快速检查,只在必要时详细分析
- 使用取消令牌:允许 IDE 取消长时间运行的分析
相关文档
RegisterSemanticModelAction - 语义模型分析
分析整个语义模型:
csharp
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class SemanticModelAnalyzerExample : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(
id: "SEMANTIC001",
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.RegisterSemanticModelAction(AnalyzeSemanticModel);
}
private void AnalyzeSemanticModel(SemanticModelAnalysisContext context)
{
var semanticModel = context.SemanticModel;
var root = semanticModel.SyntaxTree.GetRoot(context.CancellationToken);
var typeCount = root.DescendantNodes()
.OfType<TypeDeclarationSyntax>()
.Count();
if (typeCount > 0)
{
var diagnostic = Diagnostic.Create(
Rule,
Location.None,
typeCount);
context.ReportDiagnostic(diagnostic);
}
}
}RegisterCompilationAction - 编译分析
分析整个编译:
csharp
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class CompilationAnalyzerExample : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(
id: "COMP001",
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.RegisterCompilationAction(AnalyzeCompilation);
}
private void AnalyzeCompilation(CompilationAnalysisContext context)
{
var compilation = context.Compilation;
var typeCount = compilation.Assembly.GlobalNamespace
.GetAllTypes()
.Count();
var diagnostic = Diagnostic.Create(
Rule,
Location.None,
typeCount);
context.ReportDiagnostic(diagnostic);
}
}
public static class NamespaceSymbolExtensions
{
public static IEnumerable<INamedTypeSymbol> GetAllTypes(
this INamespaceSymbol namespaceSymbol)
{
foreach (var member in namespaceSymbol.GetMembers())
{
if (member is INamedTypeSymbol type)
{
yield return type;
}
else if (member is INamespaceSymbol childNamespace)
{
foreach (var childType in GetAllTypes(childNamespace))
{
yield return childType;
}
}
}
}
}完整的分析器示例
以下是一个完整的分析器示例,检查循环中的对象创建:
csharp
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using System.Collections.Immutable;
using System.Linq;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class AvoidObjectCreationInLoopAnalyzer : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(
id: "PERF001",
title: "避免在循环中创建对象",
messageFormat: "在循环中创建 '{0}' 对象可能导致性能问题,考虑在循环外创建或使用对象池",
category: "Performance",
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true,
description: "在循环中重复创建对象会增加 GC 压力,影响性能。应该考虑对象重用或使用对象池。",
helpLinkUri: "https://docs.example.com/performance/object-creation");
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
ImmutableArray.Create(Rule);
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterSyntaxNodeAction(
AnalyzeLoop,
SyntaxKind.ForStatement,
SyntaxKind.ForEachStatement,
SyntaxKind.WhileStatement,
SyntaxKind.DoStatement);
}
private void AnalyzeLoop(SyntaxNodeAnalysisContext context)
{
var loopStatement = context.Node;
var loopBody = GetLoopBody(loopStatement);
if (loopBody == null)
return;
var objectCreations = loopBody.DescendantNodes()
.OfType<ObjectCreationExpressionSyntax>();
foreach (var objectCreation in objectCreations)
{
var typeInfo = context.SemanticModel.GetTypeInfo(
objectCreation,
context.CancellationToken);
if (typeInfo.Type != null && ShouldReport(typeInfo.Type))
{
var diagnostic = Diagnostic.Create(
Rule,
objectCreation.GetLocation(),
typeInfo.Type.Name);
context.ReportDiagnostic(diagnostic);
}
}
}
private StatementSyntax GetLoopBody(SyntaxNode loopStatement)
{
return loopStatement switch
{
ForStatementSyntax forStatement => forStatement.Statement,
ForEachStatementSyntax forEachStatement => forEachStatement.Statement,
WhileStatementSyntax whileStatement => whileStatement.Statement,
DoStatementSyntax doStatement => doStatement.Statement,
_ => null
};
}
private bool ShouldReport(ITypeSymbol type)
{
if (type.IsValueType)
return false;
if (type.SpecialType == SpecialType.System_String)
return false;
if (type.TypeKind == TypeKind.Delegate)
return false;
return true;
}
}性能优化技巧
- 缓存语义查询结果
- 使用快速路径检查
- 避免在循环中进行语义查询
- 使用不可变集合
- 处理取消请求
常见问题
Q: 如何选择合适的分析动作?
A: 根据需要的信息选择:
- 只需要语法信息 → RegisterSyntaxNodeAction
- 需要类型信息 → RegisterSymbolAction
- 需要分析整个文件 → RegisterSemanticModelAction
- 需要分析整个项目 → RegisterCompilationAction
Q: 如何提高分析器性能?
A:
- 启用并发执行
- 使用合适的分析动作
- 快速检查后再详细分析
- 缓存重复查询的结果
- 避免耗时操作