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

Roslyn分析器遇方法重载时,如何获取InvocationExpression对应的ISymbol?

Roslyn分析器中如何获取重载方法调用对应的ISymbol?

我正在开发一个Roslyn代码分析器,用于在ASP.NET Core MVC(基于EF Core)应用中,检测使用同步数据库方法且存在异步替代版本的场景并发出警告——因为同步数据库调用会导致线程耗尽,而EF Core的IQueryable扩展方法遵循命名约定,比如First对应FirstAsync。

核心需求是先识别同步方法调用,再查找对应的异步替代方法。但遇到了问题:当找到同步方法调用的InvocationExpressionSyntax后,调用SemanticModel.GetSymbolInfo获取ISymbol时,只要方法存在重载,就会返回CandidateReason.OverloadResolutionFailure和CandidateSymbols数组,无法直接得到语法对应的符号。

我曾看到2019年的Gitter对话提到“语言本身没有这样的概念,得自己处理”,还有一段手动匹配参数推断重载的示例代码,但没能跑通。想知道:有没有办法让Roslyn直接返回语法对应的符号?如果没有,该如何手动实现?


更新1

当前的方法调用不存在歧义,调用表达式为:

context.Users.First(u => u.Id == 1)

候选符号有两个:

DbSet<User>.First()
DbSet<User>.First(Expression<Func<User, bool>>)

更新2

测试源代码

using System.Linq.Expressions;

public abstract class DbSet<TEntity> where TEntity : class {
    public TEntity First() {
        throw new Exception();
    }

    public TEntity First(Expression<Func<TEntity, bool>> predicate) {
        throw new Exception();
    }

    public Task<TEntity> FirstAsync(CancellationToken cancellationToken = default) {
        throw new Exception();
    }

    public Task<TEntity> FirstAsync<TSource>(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default) {
        throw new Exception();
    }
}

public class User {
    public int Id { get; set; }
}

public class ApplicationDbContext {
    public DbSet<User> Users { get; set; } = null!;
}

public class Test {
    public void TestContext(ApplicationDbContext context) {
        var x = context.Users.First();
        var y = context.Users.First(u => u.Id == 1);
    }
}

分析器代码

private void AnalyzeSyncInvocationExpression(SyntaxNodeAnalysisContext context) {
    var invocationExpr = (InvocationExpressionSyntax)context.Node;

    // 只关注非异步等待的同步调用
    if (invocationExpr.Parent is AwaitExpressionSyntax) {
        return;
    }

    // 排除调用链中的中间节点,只处理完整的调用表达式
    if (invocationExpr.Parent is MemberAccessExpressionSyntax) {
        return;
    }

    // 如果返回值是IQueryable,说明是查询构建阶段,不触发警告
    if (context.SemanticModel.GetTypeInfo(invocationExpr).Type is INamedTypeSymbol returnType && returnType.Name == nameof(IQueryable)) {
        return;
    }

    // 获取调用的方法符号
    var invokedSymbol = context.SemanticModel.GetSymbolInfo(invocationExpr).Symbol;

    if (invokedSymbol == null) {
        return; // 无法确定符号时直接返回
    }

    // 此处存在问题:重载场景下invokedSymbol为null
}

解决方案

1. 修复测试代码中的方法定义问题

你的测试代码里,DbSet<TEntity>.FirstAsync<TSource>的泛型参数TSource是多余的——EF Core实际的异步方法不会包含这个额外泛型参数,这会干扰Roslyn的重载解析逻辑,导致无法直接返回正确符号。先修正异步方法定义:

public Task<TEntity> FirstAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default) {
    throw new Exception();
}

修正后,GetSymbolInfo(invocationExpr).Symbol就能正确返回对应的重载符号。

2. 手动处理重载解析(通用方案)

如果需要处理复杂重载场景,或者Roslyn无法自动解析时,可以手动对比候选符号与调用表达式的参数,匹配正确的重载:

private IMethodSymbol? ResolveCorrectOverload(InvocationExpressionSyntax invocationExpr, SemanticModel semanticModel)
{
    var symbolInfo = semanticModel.GetSymbolInfo(invocationExpr);
    if (symbolInfo.Symbol != null)
    {
        return symbolInfo.Symbol as IMethodSymbol;
    }

    var candidateMethods = symbolInfo.CandidateSymbols.OfType<IMethodSymbol>();
    var arguments = invocationExpr.ArgumentList.Arguments;

    foreach (var method in candidateMethods)
    {
        var parameters = method.Parameters;
        // 匹配参数数量(含可选参数)
        if (arguments.Count < parameters.Count(p => !p.IsOptional) || arguments.Count > parameters.Count)
        {
            continue;
        }

        bool matches = true;
        for (int i = 0; i < arguments.Count; i++)
        {
            var arg = arguments[i];
            var param = parameters[i];

            var argType = semanticModel.GetTypeInfo(arg.Expression).Type;
            if (argType == null)
            {
                matches = false;
                break;
            }

            // 检查参数类型是否支持隐式转换
            var conversion = semanticModel.ClassifyConversion(arg.Expression, param.Type);
            if (!conversion.IsImplicit)
            {
                matches = false;
                break;
            }
        }

        if (matches)
        {
            return method;
        }
    }

    return null;
}

在分析器中替换原符号获取逻辑:

// 替换原来的invokedSymbol获取逻辑
var invokedSymbol = ResolveCorrectOverload(invocationExpr, context.SemanticModel);

if (invokedSymbol == null) {
    return;
}

3. EF Core场景额外优化

  • 先过滤名称不含Async的方法,再检查是否存在对应XxxAsync命名的方法,减少无效匹配
  • 验证方法所属程序集是否为EF Core相关程序集,降低误判概率

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 06:54:36