Skip to content

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);
}

最佳实践

  1. 启用并发执行:context.EnableConcurrentExecution()
  2. 配置生成代码分析:context.ConfigureGeneratedCodeAnalysis()
  3. 使用合适的分析动作:选择最合适的分析阶段
  4. 避免耗时操作:快速检查,只在必要时详细分析
  5. 使用取消令牌:允许 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;
    }
}

性能优化技巧

  1. 缓存语义查询结果
  2. 使用快速路径检查
  3. 避免在循环中进行语义查询
  4. 使用不可变集合
  5. 处理取消请求

常见问题

Q: 如何选择合适的分析动作?

A: 根据需要的信息选择:

  • 只需要语法信息 → RegisterSyntaxNodeAction
  • 需要类型信息 → RegisterSymbolAction
  • 需要分析整个文件 → RegisterSemanticModelAction
  • 需要分析整个项目 → RegisterCompilationAction

Q: 如何提高分析器性能?

A:

  1. 启用并发执行
  2. 使用合适的分析动作
  3. 快速检查后再详细分析
  4. 缓存重复查询的结果
  5. 避免耗时操作

基于 MIT 许可发布