You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用C# Roslyn分析器检测枚举值ToString调用并替换为nameof

方案可行性判断

你提到的只监听SyntaxKind.InvocationExpression检测枚举.ToString()调用的方案只能覆盖显式调用ToString的场景,无法覆盖你示例中所有隐式将枚举转换为string的场景,包括:

  • 直接把枚举值传给接收string类型参数的方法
  • 字符串插值中未显式调用ToString的枚举
  • 字符串+拼接操作中的枚举值
    因此该方案不能满足全场景覆盖的需求。
最优实现方案

你可以拆分场景分别注册语法节点监听,同时抽离公共判断逻辑避免重复的生硬实现:

1. 抽离通用枚举转换判断逻辑

private static bool TryGetEnumExpression(ExpressionSyntax expr, SemanticModel semanticModel, out ExpressionSyntax enumAccessExpr)
{
    enumAccessExpr = null;
    // 剥掉显式无参ToString调用的外层
    if (expr is InvocationExpressionSyntax invocation 
        && invocation.Expression is MemberAccessExpressionSyntax memberAccess 
        && memberAccess.Name.Identifier.Text == nameof(ToString)
        && invocation.ArgumentList.Arguments.Count == 0)
    {
        expr = memberAccess.Expression;
    }
    // 判断核心表达式是否为枚举类型
    var typeInfo = semanticModel.GetTypeInfo(expr);
    if (typeInfo.Type?.TypeKind == TypeKind.Enum)
    {
        enumAccessExpr = expr;
        return true;
    }
    return false;
}

该方法自动兼容显式/隐式调用ToString的场景,直接返回枚举访问节点,避免你之前使用Split拆分字符串的生硬逻辑。

2. 分别实现各场景的检测逻辑

显式ToString调用检测

private void AnalyzeInvocation(SyntaxNodeAnalysisContext ctx)
{
    var invocation = (InvocationExpressionSyntax)ctx.Node;
    if (TryGetEnumExpression(invocation, ctx.SemanticModel, out var enumAccess))
    {
        ctx.ReportDiagnostic(Diagnostic.Create(
            Descriptors.UseNameOfForEnum, 
            invocation.GetLocation(), 
            $"nameof({enumAccess})"));
    }
}

字符串插值场景检测

private void AnalyzeInterpolatedString(SyntaxNodeAnalysisContext ctx)
{
    var interpolationSyntax = (InterpolatedStringExpressionSyntax)ctx.Node;
    foreach (var chunk in interpolationSyntax.Contents.OfType<InterpolationSyntax>())
    {
        if (TryGetEnumExpression(chunk.Expression, ctx.SemanticModel, out var enumAccess))
        {
            ctx.ReportDiagnostic(Diagnostic.Create(
                Descriptors.UseNameOfForEnum, 
                chunk.GetLocation(), 
                $"nameof({enumAccess})"));
        }
    }
}

字符串+拼接场景检测

private void AnalyzeAddExpression(SyntaxNodeAnalysisContext ctx)
{
    var addExpr = (BinaryExpressionSyntax)ctx.Node;
    CheckSide(addExpr.Left);
    CheckSide(addExpr.Right);

    void CheckSide(ExpressionSyntax side)
    {
        if (TryGetEnumExpression(side, ctx.SemanticModel, out var enumAccess))
        {
            ctx.ReportDiagnostic(Diagnostic.Create(
                Descriptors.UseNameOfForEnum, 
                side.GetLocation(), 
                $"nameof({enumAccess})"));
        }
    }
}

方法参数隐式转换场景检测

private void AnalyzeArgument(SyntaxNodeAnalysisContext ctx)
{
    var arg = (ArgumentSyntax)ctx.Node;
    if (arg.Parent is not ArgumentListSyntax argList) return;
    var methodSymbol = ctx.SemanticModel.GetSymbolInfo(argList.Parent).Symbol as IMethodSymbol;
    if (methodSymbol == null) return;
    
    var paramIdx = argList.Arguments.IndexOf(arg);
    if (paramIdx < 0 || methodSymbol.Parameters.Length <= paramIdx 
        || methodSymbol.Parameters[paramIdx].Type.SpecialType != SpecialType.System_String)
        return;

    if (TryGetEnumExpression(arg.Expression, ctx.SemanticModel, out var enumAccess))
    {
        ctx.ReportDiagnostic(Diagnostic.Create(
            Descriptors.UseNameOfForEnum, 
            arg.GetLocation(), 
            $"nameof({enumAccess})"));
    }
}

3. 注册所有监听节点

public override void Initialize(AnalysisContext context)
{
    context.EnableConcurrentExecution();
    context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);

    context.RegisterSyntaxNodeAction(AnalyzeInterpolatedString, SyntaxKind.InterpolatedStringExpression);
    context.RegisterSyntaxNodeAction(AnalyzeInvocation, SyntaxKind.InvocationExpression);
    context.RegisterSyntaxNodeAction(AnalyzeAddExpression, SyntaxKind.AddExpression);
    context.RegisterSyntaxNodeAction(AnalyzeArgument, SyntaxKind.Argument);
}

额外优化点

  • 自动过滤带参数的ToString重载场景,比如ToString("D")这种需要输出枚举数值的场景不会误触发提示
  • 诊断位置精确到枚举访问节点本身,而不是整个外层表达式,用户定位更准确
  • 代码修复提供器可直接用返回的enumAccess节点生成nameof表达式,无需手动处理字符串拼接

内容的提问来源于stack exchange,提问作者Duu82

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.01 05:39:02