MST

星途 面试题库

面试题:C# Roslyn Analyzer 的复杂规则定制

假设你要开发一个 Roslyn Analyzer,用于检测项目中所有继承自特定抽象类的子类,要求子类必须重写抽象类中的所有抽象方法,并且重写方法的参数个数和类型必须与抽象方法一致。请详细阐述实现思路,包括如何遍历语法树、如何获取和比较方法签名等关键操作。
42.5万 热度难度
编程语言C#

知识考点

AI 面试

面试题答案

一键面试
  1. 创建 Roslyn Analyzer 项目
    • 使用 Visual Studio 创建一个新的“代码分析器库”项目。这将为我们提供一个基础结构,用于编写自定义的 Roslyn Analyzer。
  2. 遍历语法树
    • 在 Analyzer 的 Initialize 方法中注册 SyntaxNodeAction。对于 C#项目,我们主要关注 ClassDeclarationSyntax 节点。
    • 例如:
    context.RegisterSyntaxNodeAction(AnalyzeClass, SyntaxKind.ClassDeclaration);
    
    • 然后在 AnalyzeClass 方法中,获取当前分析的 ClassDeclarationSyntax 对象。通过 ClassDeclarationSyntax.BaseList 属性检查该类是否继承自特定的抽象类。
    private void AnalyzeClass(SyntaxNodeAnalysisContext context)
    {
        var classDeclaration = (ClassDeclarationSyntax)context.Node;
        if (classDeclaration.BaseList!= null)
        {
            foreach (var type in classDeclaration.BaseList.Types)
            {
                var baseTypeSymbol = context.SemanticModel.GetSymbolInfo(type.Type).Symbol as INamedTypeSymbol;
                if (baseTypeSymbol!= null && baseTypeSymbol.IsAbstract && baseTypeSymbol.Name == "YourAbstractClassName")
                {
                    // 找到了继承自特定抽象类的子类
                    // 开始分析方法重写
                }
            }
        }
    }
    
  3. 获取抽象类的抽象方法
    • 当确定一个类继承自特定抽象类后,通过 INamedTypeSymbol 获取抽象类的所有抽象方法。
    var abstractMethods = baseTypeSymbol.GetMembers().OfType<IMethodSymbol>()
       .Where(m => m.IsAbstract &&!m.IsStatic);
    
  4. 获取子类的重写方法
    • 在子类的 ClassDeclarationSyntax 中,获取所有的方法声明 MethodDeclarationSyntax
    var subclassMethods = classDeclaration.Members.OfType<MethodDeclarationSyntax>();
    
    • 对于每个 MethodDeclarationSyntax,通过 SemanticModel 获取对应的 IMethodSymbol
    foreach (var method in subclassMethods)
    {
        var methodSymbol = context.SemanticModel.GetDeclaredSymbol(method) as IMethodSymbol;
        if (methodSymbol!= null)
        {
            // 处理方法签名比较
        }
    }
    
  5. 比较方法签名
    • 对于抽象类的每个抽象方法和子类的每个方法,比较它们的参数个数和参数类型。
    • 参数个数比较:
    if (abstractMethod.Parameters.Length!= subclassMethod.Parameters.Length)
    {
        // 报告错误,参数个数不一致
        context.ReportDiagnostic(Diagnostic.Create(Rule, method.GetLocation(), $"重写方法 {subclassMethod.Identifier.Text} 的参数个数与抽象方法 {abstractMethod.Name} 不一致"));
    }
    
    • 参数类型比较:
    for (int i = 0; i < abstractMethod.Parameters.Length; i++)
    {
        if (abstractMethod.Parameters[i].Type.ToDisplayString()!= subclassMethod.Parameters[i].Type.ToDisplayString())
        {
            // 报告错误,参数类型不一致
            context.ReportDiagnostic(Diagnostic.Create(Rule, method.GetLocation(), $"重写方法 {subclassMethod.Identifier.Text} 的第 {i + 1} 个参数类型与抽象方法 {abstractMethod.Name} 不一致"));
        }
    }
    
  6. 报告诊断信息
    • 如果发现子类没有重写抽象类的某个抽象方法,或者重写方法的参数个数或类型不一致,通过 SyntaxNodeAnalysisContext.ReportDiagnostic 方法报告诊断信息。
    • 首先需要定义一个 DiagnosticDescriptor 来描述错误规则,例如:
    private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(
        "YourDiagnosticId",
        "子类必须正确重写抽象类的抽象方法",
        "子类 {0} 没有正确重写抽象类 {1} 的抽象方法 {2}",
        "Naming",
        DiagnosticSeverity.Error,
        isEnabledByDefault: true);
    
    • 然后在发现问题时调用 context.ReportDiagnostic(Diagnostic.Create(Rule, method.GetLocation(), classDeclaration.Identifier.Text, baseTypeSymbol.Name, abstractMethod.Name)); 来报告错误。